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

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

There is a newer version: 8.6.0.37351
Show newest version
/*
 * SonarQube Java
 * Copyright (C) 2012-2024 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 Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * 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 Sonar Source-Available License for more details.
 *
 * You should have received a copy of the Sonar Source-Available License
 * along with this program; if not, see https://sonarsource.com/license/ssal/
 */
package org.sonar.java.checks;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaVersionAwareVisitor;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.java.model.SyntacticEquivalence;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.BinaryExpressionTree;
import org.sonar.plugins.java.api.tree.ConditionalExpressionTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.ForStatementTree;
import org.sonar.plugins.java.api.tree.IfStatementTree;
import org.sonar.plugins.java.api.tree.InstanceOfTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TypeCastTree;
import org.sonar.plugins.java.api.tree.UnaryExpressionTree;
import org.sonar.plugins.java.api.tree.VariableTree;
import org.sonar.plugins.java.api.tree.WhileStatementTree;

@Rule(key = "S6201")
public class InstanceOfPatternMatchingCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor {
  @Override
  public List nodesToVisit() {
    return Arrays.asList(Tree.Kind.IF_STATEMENT, Tree.Kind.FOR_STATEMENT, Tree.Kind.WHILE_STATEMENT,
      Tree.Kind.CONDITIONAL_AND, Tree.Kind.CONDITIONAL_OR, Tree.Kind.CONDITIONAL_EXPRESSION);
  }

  @Override
  public boolean isCompatibleWithJavaVersion(JavaVersion version) {
    return version.isJava16Compatible();
  }

  @Override
  public void visitNode(Tree tree) {
    switch (tree.kind()) {
      case IF_STATEMENT:
        IfStatementTree ifStatement = (IfStatementTree) tree;
        handleConditional(ifStatement.condition(), ifStatement.thenStatement(), ifStatement.elseStatement());
        break;
      case FOR_STATEMENT:
        ForStatementTree forStatement = (ForStatementTree) tree;
        if (forStatement.condition() != null) {
          // Technically a negated instanceof inside a for- or while-condition should make us look for casts that
          // come after the loop, but for that we'd need the CFG and that seems overkill for this rule.
          handleConditional(forStatement.condition(), forStatement.statement(), null);
        }
        break;
      case WHILE_STATEMENT:
        WhileStatementTree whileStatement = (WhileStatementTree) tree;
        handleConditional(whileStatement.condition(), whileStatement.statement(), null);
        break;
      case CONDITIONAL_AND:
        BinaryExpressionTree and = (BinaryExpressionTree) tree;
        handleConditional(and.leftOperand(), and.rightOperand(), null);
        break;
      case CONDITIONAL_OR:
        BinaryExpressionTree or = (BinaryExpressionTree) tree;
        handleConditional(or.leftOperand(), null, or.rightOperand());
        break;
      default: // CONDITIONAL_EXPRESSION
        ConditionalExpressionTree conditional = (ConditionalExpressionTree) tree;
        handleConditional(conditional.condition(), conditional.trueExpression(), conditional.falseExpression());
        break;
    }
  }

  private void handleConditional(ExpressionTree condition, @Nullable Tree thenBody, @Nullable Tree elseBody) {
    findInstanceOf(condition).ifPresent(instanceOf -> {
      if (!instanceOf.negated && thenBody != null) {
        thenBody.accept(new BodyVisitor(instanceOf.tree));
      } else if (instanceOf.negated && elseBody != null) {
        elseBody.accept(new BodyVisitor(instanceOf.tree));
      }
    });
  }

  // Note: If a condition contains multiple instanceof checks, we'll only return (and thus only check) the first one.
  // The number of FNs resulting from that should be small and in the case that's probably the most common
  // (`if (x instanceof X && y instanceof Y) { X x = (X) x; Y y = (Y) y;}`), we'll still find the second issue after the
  // first has been fixed.
  private static Optional findInstanceOf(ExpressionTree condition) {
    return findInstanceOf(condition, false);
  }

  private static Optional findInstanceOf(ExpressionTree condition, boolean negated) {
    condition = ExpressionUtils.skipParentheses(condition);
    switch (condition.kind()) {
      case INSTANCE_OF:
        return Optional.of(new InstanceOfInfo((InstanceOfTree) condition, negated));
      case CONDITIONAL_AND:
        // If the condition is part of a negated AND, it won't dominate the else case of the if because only one of the
        // operands of the AND would have to be
        if (negated) return Optional.empty();
        return findInstanceOfInBinaryExpression(condition, negated);
      case CONDITIONAL_OR:
        if (!negated) return Optional.empty();
        return findInstanceOfInBinaryExpression(condition, negated);
      case LOGICAL_COMPLEMENT:
        return findInstanceOf(((UnaryExpressionTree) condition).expression(), !negated);
      default:
        return Optional.empty();
    }
  }

  private static Optional findInstanceOfInBinaryExpression(ExpressionTree condition, boolean negated) {
    BinaryExpressionTree binaryExpression = (BinaryExpressionTree) condition;
    Optional leftResult = findInstanceOf(binaryExpression.leftOperand(), negated);
    if (leftResult.isPresent()) return leftResult;
    return findInstanceOf(binaryExpression.rightOperand(), negated);
  }

  private static class InstanceOfInfo {
    InstanceOfTree tree;
    boolean negated;

    public InstanceOfInfo(InstanceOfTree tree, boolean negated) {
      this.tree = tree;
      this.negated = negated;
    }
  }

  private class BodyVisitor extends BaseTreeVisitor {
    InstanceOfTree instanceOf;

    BodyVisitor(InstanceOfTree instanceOf) {
      this.instanceOf = instanceOf;
    }

    @Override
    public void visitVariable(VariableTree tree) {
      Type type = tree.type().symbolType();
      if (!type.isUnknown() && type.equals(instanceOf.type().symbolType())) {
        ExpressionTree init = tree.initializer();
        if (init != null && init.is(Tree.Kind.TYPE_CAST)
          && SyntacticEquivalence.areEquivalentIncludingSameVariables(((TypeCastTree) init).expression(), instanceOf.expression())) {
          report(instanceOf, init, tree.simpleName().name());
          return;
        }
      }
      super.visitVariable(tree);
    }

    @Override
    public void visitTypeCast(TypeCastTree tree) {
      Type type = tree.symbolType();
      if (!type.isUnknown() && type.equals(instanceOf.type().symbolType())
        && SyntacticEquivalence.areEquivalentIncludingSameVariables(tree.expression(), instanceOf.expression())) {
        report(instanceOf, tree, tree.type().symbolType().name().toLowerCase(Locale.ROOT));
      }
    }

    private void report(InstanceOfTree instanceOf, ExpressionTree cast, String name) {
      String type = instanceOf.type().symbolType().name();
      String message = String.format("Replace this instanceof check and cast with 'instanceof %s %s'", type, name);
      JavaFileScannerContext.Location secondary = new JavaFileScannerContext.Location("Location of the cast", cast);
      reportIssue(instanceOf, message, Collections.singletonList(secondary), null);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy