org.sonar.java.checks.OverwrittenKeyCheck.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.java.checks.OverwrittenKeyCheck.java

Source

/*
 * SonarQube Java
 * Copyright (C) 2012-2019 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.java.checks;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import org.sonar.check.Rule;
import org.sonar.java.matcher.MethodMatcher;
import org.sonar.java.matcher.TypeCriteria;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.ArrayAccessExpressionTree;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.BlockTree;
import org.sonar.plugins.java.api.tree.ExpressionStatementTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.StatementTree;
import org.sonar.plugins.java.api.tree.Tree;

import javax.annotation.CheckForNull;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Rule(key = "S4143")
public class OverwrittenKeyCheck extends IssuableSubscriptionVisitor {

    private static final MethodMatcher MAP_PUT = MethodMatcher.create()
            .typeDefinition(TypeCriteria.subtypeOf("java.util.Map")).name("put")
            .parameters(TypeCriteria.anyType(), TypeCriteria.anyType());

    @Override
    public List<Tree.Kind> nodesToVisit() {
        return Collections.singletonList(Tree.Kind.BLOCK);
    }

    @Override
    public void visitNode(Tree tree) {
        if (!hasSemantic()) {
            return;
        }

        ListMultimap<CollectionAndKey, Tree> usedKeys = ArrayListMultimap.create();
        for (StatementTree statementTree : ((BlockTree) tree).body()) {
            CollectionAndKey mapPut = isMapPut(statementTree);
            if (mapPut != null) {
                usedKeys.put(mapPut, mapPut.keyTree);
            } else {
                CollectionAndKey arrayAssignment = isArrayAssignment(statementTree);
                if (arrayAssignment != null) {
                    if (arrayAssignment.collectionOnRHS()) {
                        usedKeys.clear();
                    }
                    usedKeys.put(arrayAssignment, arrayAssignment.keyTree);
                } else {
                    // sequence of setting collection values is interrupted
                    reportOverwrittenKeys(usedKeys);
                    usedKeys.clear();
                }
            }
        }
        reportOverwrittenKeys(usedKeys);
    }

    private void reportOverwrittenKeys(ListMultimap<CollectionAndKey, Tree> usedKeys) {
        Multimaps.asMap(usedKeys).forEach((key, trees) -> {
            if (trees.size() > 1) {
                Tree firstUse = trees.get(0);
                Tree firstOverwrite = trees.get(1);
                List<Tree> rest = trees.subList(2, trees.size());
                reportIssue(firstOverwrite,
                        "Verify this is the " + key.indexOrKey() + " that was intended; it was already set before.",
                        secondaryLocations(key, firstUse, rest), 0);
            }
        });
    }

    private static List<JavaFileScannerContext.Location> secondaryLocations(CollectionAndKey key, Tree firstUse,
            List<Tree> rest) {
        return Stream
                .concat(Stream.of(new JavaFileScannerContext.Location("Original value", firstUse)), rest.stream()
                        .map(t -> new JavaFileScannerContext.Location("Same " + key.indexOrKey() + " is set", t)))
                .collect(Collectors.toList());
    }

    private static class CollectionAndKey {
        private final Symbol collection;
        private final Tree keyTree;
        private final Object key;
        private final boolean isArray;
        private ExpressionTree rhs;

        private CollectionAndKey(Symbol collection, Tree keyTree, Object key, boolean isArray,
                ExpressionTree expression) {
            this.collection = collection;
            this.keyTree = keyTree;
            this.key = key;
            this.isArray = isArray;
            this.rhs = expression;
        }

        private boolean collectionOnRHS() {
            FindSymbolUsage findSymbolUsage = new FindSymbolUsage(collection);
            rhs.accept(findSymbolUsage);
            return findSymbolUsage.used;
        }

        private String indexOrKey() {
            return isArray ? "index" : "key";
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            CollectionAndKey that = (CollectionAndKey) o;
            return Objects.equals(collection, that.collection) && Objects.equals(key, that.key);
        }

        @Override
        public int hashCode() {
            return Objects.hash(collection, key);
        }
    }

    @CheckForNull
    private static Symbol symbolFromIdentifier(ExpressionTree collectionExpression) {
        if (collectionExpression.is(Tree.Kind.IDENTIFIER)) {
            Symbol symbol = ((IdentifierTree) collectionExpression).symbol();
            if (!symbol.isUnknown()) {
                return symbol;
            }
        }
        return null;
    }

    @CheckForNull
    private static CollectionAndKey isArrayAssignment(StatementTree statementTree) {
        if (statementTree.is(Tree.Kind.EXPRESSION_STATEMENT)) {
            ExpressionTree expression = ((ExpressionStatementTree) statementTree).expression();
            if (expression.is(Tree.Kind.ASSIGNMENT)) {
                AssignmentExpressionTree assignment = (AssignmentExpressionTree) expression;
                ExpressionTree variable = assignment.variable();
                if (variable.is(Tree.Kind.ARRAY_ACCESS_EXPRESSION)) {
                    ArrayAccessExpressionTree aaet = (ArrayAccessExpressionTree) variable;
                    Symbol collection = symbolFromIdentifier(aaet.expression());
                    ExpressionTree keyTree = aaet.dimension().expression();
                    Object key = extractKey(keyTree);
                    if (collection != null && key != null) {
                        return new CollectionAndKey(collection, keyTree, key, true, assignment.expression());
                    }
                }
            }
        }
        return null;
    }

    private static class FindSymbolUsage extends BaseTreeVisitor {

        private final Symbol symbol;
        private boolean used;

        public FindSymbolUsage(Symbol symbol) {
            this.symbol = symbol;
        }

        @Override
        public void visitIdentifier(IdentifierTree tree) {
            if (!used) {
                used = tree.symbol() == symbol;
            }
        }
    }

    @CheckForNull
    private static CollectionAndKey isMapPut(StatementTree statementTree) {
        if (statementTree.is(Tree.Kind.EXPRESSION_STATEMENT)) {
            ExpressionTree expression = ((ExpressionStatementTree) statementTree).expression();
            if (expression.is(Tree.Kind.METHOD_INVOCATION) && MAP_PUT.matches((MethodInvocationTree) expression)) {
                MethodInvocationTree mapPut = (MethodInvocationTree) expression;
                Symbol collection = mapPut.methodSelect().is(Tree.Kind.MEMBER_SELECT)
                        ? symbolFromIdentifier(((MemberSelectExpressionTree) mapPut.methodSelect()).expression())
                        : null;
                ExpressionTree keyTree = mapPut.arguments().get(0);
                Object key = extractKey(keyTree);
                if (collection != null && key != null) {
                    return new CollectionAndKey(collection, keyTree, key, false, null);
                }
            }
        }
        return null;
    }

    @CheckForNull
    private static Object extractKey(ExpressionTree keyArgument) {
        if (keyArgument instanceof LiteralTree) {
            return ((LiteralTree) keyArgument).value();
        }
        return symbolFromIdentifier(keyArgument);
    }
}