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

org.sonar.java.checks.PrivateFieldUsedLocallyCheck 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.ImmutableList;
import org.sonar.check.Rule;
import org.sonar.java.cfg.CFG;
import org.sonar.java.cfg.LiveVariables;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Symbol.TypeSymbol;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
import org.sonar.plugins.java.api.tree.VariableTree;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static org.sonar.java.se.ProgramState.isField;

/**
 * Current implementation raises the issue only for the fields used in one method
 */
@Rule(key = "S1450")
public class PrivateFieldUsedLocallyCheck extends IssuableSubscriptionVisitor {

  private static final String MESSAGE = "Remove the \"%s\" field and declare it as a local variable in the relevant methods.";

  @Override
  public List nodesToVisit() {
    return ImmutableList.of(Kind.CLASS);
  }

  @Override
  public void visitNode(Tree tree) {
    TypeSymbol classSymbol = ((ClassTree) tree).symbol();
    Set fieldsReadOnAnotherInstance = FieldsReadOnAnotherInstanceVisitor.getFrom(tree);

    classSymbol.memberSymbols().stream()
      .filter(PrivateFieldUsedLocallyCheck::isPrivateField)
      .filter(s -> !(s.isFinal() && s.isStatic()))
      .filter(s -> !hasAnnotation(s))
      .filter(s -> !s.usages().isEmpty())
      .filter(s -> !fieldsReadOnAnotherInstance.contains(s))
      .forEach(s -> checkPrivateField(s, classSymbol));
  }

  private static boolean hasAnnotation(Symbol s) {
    return !s.metadata().annotations().isEmpty();
  }

  private void checkPrivateField(Symbol privateFieldSymbol, TypeSymbol classSymbol) {
    MethodTree methodWhereUsed = usedInOneMethodOnly(privateFieldSymbol, classSymbol);

    if (methodWhereUsed != null && !isLiveInMethodEntry(privateFieldSymbol, methodWhereUsed)) {
      IdentifierTree declarationIdentifier = ((VariableTree) privateFieldSymbol.declaration()).simpleName();
      String message = String.format(MESSAGE, privateFieldSymbol.name());
      reportIssue(declarationIdentifier, message);
    }
  }

  private static boolean isLiveInMethodEntry(Symbol privateFieldSymbol, MethodTree methodTree) {
    CFG cfg = CFG.build(methodTree);
    LiveVariables liveVariables = LiveVariables.analyzeWithFields(cfg);
    return liveVariables.getIn(cfg.entry()).contains(privateFieldSymbol);
  }

  private static boolean isPrivateField(Symbol memberSymbol) {
    return memberSymbol.isPrivate() && memberSymbol.isVariableSymbol();
  }

  /**
   * If private field used in several methods then returns null, otherwise returns the method where it's used
   */
  @CheckForNull
  private static MethodTree usedInOneMethodOnly(Symbol privateFieldSymbol, TypeSymbol classSymbol) {
    MethodTree method = null;

    for (IdentifierTree usageIdentifier : privateFieldSymbol.usages()) {
      Tree containingClassOrMethod = containingClassOrMethod(usageIdentifier);

      if (noContainerOrClassContainer(containingClassOrMethod)
        || !((MethodTree) containingClassOrMethod).symbol().owner().equals(classSymbol)
        || (method != null && !method.equals(containingClassOrMethod))) {
        return null;

      } else {
        method = (MethodTree)containingClassOrMethod;

      }
    }

    return method;
  }

  private static boolean noContainerOrClassContainer(@Nullable Tree containingClassOrMethod) {
    return containingClassOrMethod == null || containingClassOrMethod.is(Kind.CLASS, Kind.INTERFACE, Kind.ENUM, Kind.ANNOTATION_TYPE);
  }

  private static Tree containingClassOrMethod(IdentifierTree usageIdentifier) {
    Tree parent = usageIdentifier;
    do {
      parent = parent.parent();
    } while (!parent.is(Kind.METHOD, Kind.CLASS, Kind.INTERFACE, Kind.ENUM, Kind.ANNOTATION_TYPE));

    return parent;
  }

  private static class FieldsReadOnAnotherInstanceVisitor extends BaseTreeVisitor {

    private Set fieldsReadOnAnotherInstance = new HashSet<>();

    static Set getFrom(Tree classTree) {
      FieldsReadOnAnotherInstanceVisitor fieldsReadOnAnotherInstanceVisitor = new FieldsReadOnAnotherInstanceVisitor();
      fieldsReadOnAnotherInstanceVisitor.scan(classTree);
      return fieldsReadOnAnotherInstanceVisitor.fieldsReadOnAnotherInstance;
    }

    @Override
    public void visitMemberSelectExpression(MemberSelectExpressionTree tree) {
      Symbol symbol = tree.identifier().symbol();
      if (isField(symbol) && !symbol.isStatic()) {

        if (tree.expression().is(Kind.IDENTIFIER)) {
          String objectName = ((IdentifierTree) tree.expression()).name();

          if (!"this".equals(objectName)) {
            fieldsReadOnAnotherInstance.add(symbol);
          }

        } else {
          fieldsReadOnAnotherInstance.add(symbol);
        }
      }

      super.visitMemberSelectExpression(tree);
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy