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

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

There is a newer version: 8.10.0.38194
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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonarsource.analyzer.commons.collections.MapBuilder;
import org.sonarsource.analyzer.commons.collections.SetUtils;
import org.sonar.java.model.JavaTree;
import org.sonar.java.model.LineUtils;
import org.sonar.java.model.ModifiersUtils;
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.BlockTree;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Modifier;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.VariableTree;
import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey;

@DeprecatedRuleKey(ruleKey = "HiddenFieldCheck", repositoryKey = "squid")
@Rule(key = "S1117")
public class HiddenFieldCheck extends IssuableSubscriptionVisitor {

  private final Deque> fields = new LinkedList<>();
  private final Deque> excludedVariables = new LinkedList<>();
  private final Set flattenExcludedVariables = new HashSet<>();

  @Override
  public List nodesToVisit() {
    return Arrays.asList(
        Tree.Kind.CLASS,
        Tree.Kind.ENUM,
        Tree.Kind.INTERFACE,
        Tree.Kind.ANNOTATION_TYPE,
        Tree.Kind.RECORD,
        Tree.Kind.VARIABLE,
        Tree.Kind.METHOD,
        Tree.Kind.CONSTRUCTOR,
        Tree.Kind.STATIC_INITIALIZER
    );
  }

  @Override
  public void setContext(JavaFileScannerContext context) {
    fields.clear();
    excludedVariables.clear();
    flattenExcludedVariables.clear();
    super.setContext(context);
  }

  @Override
  public void visitNode(Tree tree) {
    if (isClassTree(tree)) {
      ClassTree classTree = (ClassTree) tree;
      MapBuilder builder = MapBuilder.newMap();
      for (Tree member : classTree.members()) {
        if (member.is(Tree.Kind.VARIABLE)) {
          VariableTree variableTree = (VariableTree) member;
          builder.put(variableTree.simpleName().name(), variableTree);
        }
      }
      fields.push(builder.build());
      excludedVariables.push(new ArrayList<>());
    } else if (tree.is(Tree.Kind.VARIABLE)) {
      VariableTree variableTree = (VariableTree) tree;
      isVariableHidingField(variableTree);
    } else if (tree.is(Tree.Kind.STATIC_INITIALIZER)) {
      excludeVariablesFromBlock((BlockTree) tree);
    } else {
      MethodTree methodTree = (MethodTree) tree;
      excludedVariables.peek().addAll(methodTree.parameters());
      flattenExcludedVariables.addAll(methodTree.parameters());
      if (ModifiersUtils.hasModifier(methodTree.modifiers(), Modifier.STATIC)) {
        excludeVariablesFromBlock(methodTree.block());
      }
    }
  }

  private void isVariableHidingField(VariableTree variableTree) {
    for (Map variables : fields) {
      if (variables.containsValue(variableTree)) {
        return;
      }
      String identifier = variableTree.simpleName().name();
      VariableTree hiddenVariable = variables.get(identifier);
      if (!flattenExcludedVariables.contains(variableTree) && hiddenVariable != null && !isInStaticInnerClass(hiddenVariable, variableTree)) {
        int line = LineUtils.startLine(hiddenVariable);
        reportIssue(variableTree.simpleName(), "Rename \"" + identifier + "\" which hides the field declared at line " + line + ".");
        return;
      }
    }
  }

  private static boolean isInStaticInnerClass(VariableTree hiddenVariable, VariableTree variableTree) {
    Symbol hiddenVariableOwner = hiddenVariable.symbol().owner();
    Symbol owner = variableTree.symbol().owner();
    while (owner != null && !owner.equals(hiddenVariableOwner)) {
      if (owner.isTypeSymbol() && owner.isStatic()) {
        return true;
      }
      owner = owner.owner();
    }
    return false;
  }

  private static boolean isClassTree(Tree tree) {
    return tree.is(Tree.Kind.CLASS, Tree.Kind.ENUM, Tree.Kind.INTERFACE, Tree.Kind.ANNOTATION_TYPE, Tree.Kind.RECORD);
  }

  @Override
  public void leaveNode(Tree tree) {
    if (isClassTree(tree)) {
      fields.pop();
      flattenExcludedVariables.removeAll(excludedVariables.pop());
    }
  }

  private void excludeVariablesFromBlock(@Nullable BlockTree blockTree) {
    if (blockTree != null) {
      List variableTrees = new VariableList().scan(blockTree);
      excludedVariables.peek().addAll(variableTrees);
      flattenExcludedVariables.addAll(variableTrees);
    }
  }

  private static class VariableList {

    private List variables;
    private Set visitNodes;
    private Set excludedNodes;

    List scan(Tree tree) {
      visitNodes = nodesToVisit();
      excludedNodes = excludedNodes();
      variables = new ArrayList<>();
      visit(tree);
      return variables;
    }

    public Set nodesToVisit() {
      return Collections.singleton(Tree.Kind.VARIABLE);
    }

    public Set excludedNodes() {
      return SetUtils.immutableSetOf(Tree.Kind.METHOD, Tree.Kind.CLASS, Tree.Kind.ENUM, Tree.Kind.INTERFACE, Tree.Kind.NEW_CLASS);
    }

    private void visit(Tree tree) {
      if (isSubscribed(tree)) {
        variables.add((VariableTree) tree);
      }
      visitChildren(tree);
    }

    private void visitChildren(Tree tree) {
      JavaTree javaTree = (JavaTree) tree;
      if (!javaTree.isLeaf()) {
        for (Tree next : javaTree.getChildren()) {
          if (next != null && !isExcluded(next)) {
            visit(next);
          }
        }
      }
    }

    private boolean isSubscribed(Tree tree) {
      return visitNodes.contains(tree.kind());
    }

    private boolean isExcluded(Tree tree) {
      return excludedNodes.contains(tree.kind());
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy