Package com.google.errorprone.bugpatterns

Source Code of com.google.errorprone.bugpatterns.ChainingConstructorIgnoresParameter$Caller

/*
* Copyright 2014 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.errorprone.bugpatterns;

import static com.google.common.collect.Maps.newHashMap;
import static com.google.errorprone.BugPattern.Category.JDK;
import static com.google.errorprone.BugPattern.MaturityLevel.EXPERIMENTAL;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.fixes.SuggestedFix.replace;
import static com.google.errorprone.matchers.Description.NO_MATCH;
import static com.google.errorprone.matchers.Matchers.hasIdentifier;
import static com.google.errorprone.matchers.MultiMatcher.MatchType.ANY;
import static com.google.errorprone.util.ASTHelpers.getSymbol;
import static com.google.errorprone.util.ASTHelpers.getType;
import static com.sun.source.tree.Tree.Kind.IDENTIFIER;
import static java.util.Collections.unmodifiableList;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type;

import java.util.List;
import java.util.Map;

/**
* Checks, if two constructors in a class both accept {@code Foo foo} and one calls the other, that
* the caller passes {@code foo} as a parameter. The goal is to catch copy-paste errors:<pre>
*   MissileLauncher(Location target, boolean askForConfirmation) {
*     ...
*   }
*   MissileLauncher(Location target) {
*     this(target, false);
*   }
*   MissileLauncher(boolean askForConfirmation) {
*     this(TEST_TARGET, <b>false</b>); // should be askForConfirmation
*   }</pre>
*
* @author cpovirk@google.com (Chris Povirk)
*/
@BugPattern(name = "ChainingConstructorIgnoresParameter",
    maturity = EXPERIMENTAL, category = JDK, severity = WARNING,
    explanation = "A constructor parameter might not be being used as expected",
    summary = "The called constructor accepts a parameter with the same name and type as one of "
        + "its caller's parameters, but its caller doesn't pass that parameter to it.  It's likely "
        + "that it was intended to.")
public final class ChainingConstructorIgnoresParameter extends BugChecker
    implements CompilationUnitTreeMatcher, MethodInvocationTreeMatcher, MethodTreeMatcher {
  private final Map<MethodSymbol, List<VariableTree>> paramTypesForMethod = newHashMap();
  private final Multimap<MethodSymbol, Caller> callersToEvaluate = ArrayListMultimap.create();

  @Override
  public Description matchCompilationUnit(List<? extends AnnotationTree> packageAnnotations,
      ExpressionTree packageName, List<? extends ImportTree> imports, VisitorState state) {
    /*
     * Clear the collections to save memory. (I wonder if it also helps to handle weird cases when a
     * class has multiple definitions. But I would expect for multiple definitions within the same
     * compiler invocation to cause deeper problems.)
     */
    paramTypesForMethod.clear();
    callersToEvaluate.clear(); // should have already been cleared
    return NO_MATCH;
  }

  @Override
  public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
    MethodSymbol symbol = getSymbol(tree);
    // TODO(cpovirk): determine whether anyone might be calling Foo.this()
    if (!isIdentifierWithName(tree.getMethodSelect(), "this")) {
      return NO_MATCH;
    }
    callersToEvaluate.put(symbol, new Caller(tree, state));
    return evaluateCallers(symbol);
  }

  @Override
  public Description matchMethod(MethodTree tree, VisitorState state) {
    MethodSymbol symbol = getSymbol(tree);
    if (!symbol.isConstructor()) {
      return NO_MATCH;
    }
    paramTypesForMethod.put(symbol, unmodifiableList(tree.getParameters()));
    return evaluateCallers(symbol);
  }

  private Description evaluateCallers(MethodSymbol symbol) {
    List<VariableTree> paramTypes = paramTypesForMethod.get(symbol);
    if (paramTypes == null) {
      // We haven't seen the declaration yet. We'll evaluate the call when we do.
      return NO_MATCH;
    }

    for (Caller caller : callersToEvaluate.removeAll(symbol)) {
      VisitorState state = caller.state;
      MethodInvocationTree invocation = caller.tree;

      MethodTree callerConstructor = state.findEnclosing(MethodTree.class);
      if (callerConstructor == null) {
        continue; // impossible, at least in compilable code?
      }
      Map<String, Type> availableParams = indexTypeByName(callerConstructor.getParameters());

      /*
       * TODO(cpovirk): Better handling of varargs: If the last parameter type is varargs and it is
       * called as varargs (rather than by passing an array), then rewrite the parameter types to
       * (p0, p1, ..., p[n-2], p[n-1] = element type of varargs parameter if an argument is
       * supplied, p[n] = ditto, etc.). For now, we settle for not crashing in the face of a
       * mismatch between the number of parameters declared and the number supplied.
       *
       * (Use MethodSymbol.isVarArgs.)
       */
      for (int i = 0; i < paramTypes.size() && i < invocation.getArguments().size(); i++) {
        VariableTree formalParam = paramTypes.get(i);
        String formalParamName = formalParam.getName().toString();
        Type formalParamType = getType(formalParam.getType());

        Type availableParamType = availableParams.get(formalParamName);

        ExpressionTree actualParam = invocation.getArguments().get(i);

        if (
            /*
             * The caller has no param of this type. (Or if it did, we couldn't determine the type.
             * Does that ever happen?) If the param doesn't exist, the caller can't be failing to
             * pass it.
             */
            availableParamType == null

            /*
             * We couldn't determine the type of the formal parameter. (Does this ever happen?)
             */
            || formalParamType == null

            /*
             * The caller is passing the expected parameter (or "ImmutableList.copyOf(parameter),"
             * "new File(parameter)," etc.).
             */
            || referencesIdentifierWithName(formalParamName, actualParam, state)) {
          continue;
        }

        if (state.getTypes().isAssignable(availableParamType, formalParamType)) {
          reportMatch(invocation, state, actualParam, formalParamName);
        }
        /*
         * If formal parameter is of an incompatible type, the caller might in theory still intend
         * to pass a dervied expression. For example, "Foo(String file)" might intend to call
         * "Foo(File file)" by passing "new File(file)." If this comes up in practice, we could
         * provide the dummy suggested fix "someExpression(formalParamName)." However, my research
         * suggests that this will rarely if ever be what the user wants.
         */
      }
    }

    // All matches are reported through reportMatch calls instead of return values.
    return NO_MATCH;
  }

  private static Map<String, Type> indexTypeByName(List<? extends VariableTree> parameters) {
    Map<String, Type> result = newHashMap();
    for (VariableTree parameter : parameters) {
      result.put(parameter.getName().toString(), getType(parameter.getType()));
    }
    return result;
  }

  private void reportMatch(
      Tree diagnosticPosition, VisitorState state, Tree toReplace, String replaceWith) {
    report(diagnosticPosition,
        describeMatch(diagnosticPosition, replace(toReplace, replaceWith)),
        state);
  }

  // Cloned from Scanner. This also appears in ThreadSafe. See the TODO there.
  private void report(Tree diagnosticPosition, Description description, VisitorState state) {
    state.getMatchListener().onMatch(diagnosticPosition);
    state.getDescriptionListener().onDescribed(description);
  }

  private static boolean referencesIdentifierWithName(
      final String name, ExpressionTree tree, VisitorState state) {
    Matcher<IdentifierTree> identifierMatcher = new Matcher<IdentifierTree>() {
      @Override
      public boolean matches(IdentifierTree tree, VisitorState state) {
        return isIdentifierWithName(tree, name);
      }
    };
    return hasIdentifier(ANY, identifierMatcher).matches(tree, state);
  }

  private static boolean isIdentifierWithName(ExpressionTree tree, String name) {
    return tree.getKind() == IDENTIFIER && ((IdentifierTree) tree).getName().contentEquals(name);
  }

  private static final class Caller {
    final MethodInvocationTree tree;
    final VisitorState state;

    Caller(MethodInvocationTree tree, VisitorState state) {
      this.tree = tree;
      this.state = state;
    }
  }
}
TOP

Related Classes of com.google.errorprone.bugpatterns.ChainingConstructorIgnoresParameter$Caller

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.