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

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

The newest version!
/*
 * SonarQube Java
 * Copyright (C) 2012-2025 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.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.sonar.check.Rule;
import org.sonar.java.checks.helpers.TreeHelper;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.ForEachStatement;
import org.sonar.plugins.java.api.tree.IdentifierTree;
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 org.sonar.plugins.java.api.tree.UnaryExpressionTree;
import org.sonar.plugins.java.api.tree.VariableTree;

@Rule(key = "S6909")
public class PreparedStatementLoopInvariantCheck extends IssuableSubscriptionVisitor {

  private static final MethodMatchers MATCHERS = MethodMatchers.create()
    .ofSubTypes("java.sql.PreparedStatement")
    .name(it -> it.startsWith("set"))
    .withAnyParameters()
    .build();

  private static final Set LOOP_KINDS = EnumSet.of(
    Tree.Kind.FOR_STATEMENT, Tree.Kind.FOR_EACH_STATEMENT, Tree.Kind.WHILE_STATEMENT, Tree.Kind.DO_STATEMENT
  );

  @Override
  public List nodesToVisit() {
    return List.of(Tree.Kind.METHOD);
  }

  @Override
  public void visitNode(Tree tree) {
    var invocationCollector = new MethodInvocationCollector(MATCHERS);
    tree.accept(invocationCollector);

    invocationCollector.invocations.stream()
      .map(PreparedStatementLoopInvariantCheck::getCandidate)
      .filter(Objects::nonNull)
      .collect(Collectors.groupingBy(candidate -> candidate.enclosingLoop))
      .forEach(this::checkCandidatesInLoop);
  }

  void checkCandidatesInLoop(StatementTree loop, List candidates) {
    var localsCollector = new DeclaredOrAssignedLocalsCollector();
    loop.accept(localsCollector);
    candidates.forEach(it -> reportIfLoopInvariant(localsCollector.declaredOrAssignedLocals, it));
  }

  private void reportIfLoopInvariant(Set declaredOrAssignedLocals, Candidate candidate) {
    if (isLoopInvariant(declaredOrAssignedLocals, candidate)) {
      var secondaryLocation = new JavaFileScannerContext.Location(
        "Enclosing loop",
        Objects.requireNonNull(candidate.enclosingLoop)
      );
      reportIssue(
        candidate.invocation,
        "Move this loop-invariant setter invocation out of this loop.",
        List.of(secondaryLocation),
        null
      );
    }
  }

  private static boolean isLoopInvariant(Set declaredOrAssignedLocals, Candidate candidate) {
    return (candidate.identifierArguments.stream().noneMatch(declaredOrAssignedLocals::contains));
  }

  private static Candidate getCandidate(MethodInvocationTree invocation) {
    var identifierArguments = new ArrayList();
    for (var arg : invocation.arguments()) {
      if (arg.is(Tree.Kind.IDENTIFIER)) {
        identifierArguments.add(((IdentifierTree) arg).name());
      } else {
        var argValue = ExpressionUtils.resolveAsConstant(arg);
        if (argValue == null) return null;
      }
    }

    var loop = TreeHelper.findClosestParentOfKind(invocation, LOOP_KINDS);
    if (loop != null) {
      return new Candidate(invocation, identifierArguments, (StatementTree) loop);
    }
    return null;
  }

  private static class Candidate {
    public final MethodInvocationTree invocation;
    public final List identifierArguments;
    public final StatementTree enclosingLoop;

    private Candidate(MethodInvocationTree invocation, List argumentVariables, StatementTree enclosingLoop) {
      this.invocation = invocation;
      this.identifierArguments = argumentVariables;
      this.enclosingLoop = enclosingLoop;
    }
  }

  private static class MethodInvocationCollector extends BaseTreeVisitor {

    public final List invocations = new ArrayList<>();
    private final MethodMatchers matchers;

    public MethodInvocationCollector(MethodMatchers matchers) {
      this.matchers = matchers;
    }

    @Override
    public void visitMethodInvocation(MethodInvocationTree tree) {
      if (matchers.matches(tree)) {
        invocations.add(tree);
      }
      super.visitMethodInvocation(tree);
    }
  }

  private static class DeclaredOrAssignedLocalsCollector extends BaseTreeVisitor {

    public final Set declaredOrAssignedLocals = new HashSet<>();

    @Override
    public void visitAssignmentExpression(AssignmentExpressionTree tree) {
      super.visitAssignmentExpression(tree);
      var variable = tree.variable();
      if (variable.is(Tree.Kind.IDENTIFIER)) {
        declaredOrAssignedLocals.add(((IdentifierTree) variable).name());
      }
    }

    @Override
    public void visitVariable(VariableTree tree) {
      super.visitVariable(tree);
      declaredOrAssignedLocals.add(tree.simpleName().name());
    }

    @Override
    public void visitUnaryExpression(UnaryExpressionTree tree) {
      super.visitUnaryExpression(tree);
      switch (tree.kind()) {
        case POSTFIX_INCREMENT, POSTFIX_DECREMENT, PREFIX_INCREMENT, PREFIX_DECREMENT -> {
          var expression = tree.expression();
          if (expression.is(Tree.Kind.IDENTIFIER)) {
            declaredOrAssignedLocals.add(((IdentifierTree) expression).name());
          }
        }
        default -> {
          // empty
        }
      }
    }

    @Override
    public void visitForEachStatement(ForEachStatement tree) {
      super.visitForEachStatement(tree);
      visitVariable(tree.variable());
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy