All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.checks.OverwrittenKeyCheck Maven / Gradle / Ivy

There is a newer version: 8.6.0.37351
Show newest version
/*
 * SonarQube Java
 * Copyright (C) 2012-2018 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 nodesToVisit() {
    return Collections.singletonList(Tree.Kind.BLOCK);
  }

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

    ListMultimap 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 usedKeys) {
    Multimaps.asMap(usedKeys).forEach( (key, trees) -> {
      if (trees.size() > 1) {
        Tree firstUse = trees.get(0);
        Tree firstOverwrite = trees.get(1);
        List 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 secondaryLocations(CollectionAndKey key, Tree firstUse, List 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);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy