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

framework.src.org.checkerframework.common.basetype.BaseTypeVisitor Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java’s type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.42.0
Show newest version
package org.checkerframework.common.basetype;

/*>>>
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.nullness.qual.Nullable;
*/

import org.checkerframework.dataflow.analysis.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
import org.checkerframework.dataflow.cfg.node.BooleanLiteralNode;
import org.checkerframework.dataflow.cfg.node.ExplicitThisLiteralNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.ImplicitThisLiteralNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
import org.checkerframework.dataflow.cfg.node.ThisLiteralNode;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.util.PurityChecker;
import org.checkerframework.dataflow.util.PurityChecker.PurityResult;
import org.checkerframework.dataflow.util.PurityUtils;
import org.checkerframework.framework.flow.CFAbstractStore;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.FieldIsExpression;
import org.checkerframework.framework.qual.Unused;
import org.checkerframework.framework.source.Result;
import org.checkerframework.framework.source.SourceVisitor;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.AnnotatedTypeParameterBounds;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.TypeHierarchy;
import org.checkerframework.framework.type.VisitorState;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.ContractsUtils;
import org.checkerframework.framework.util.FlowExpressionParseUtil;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionContext;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException;
import org.checkerframework.framework.util.PluginUtil;
import org.checkerframework.framework.util.QualifierPolymorphism;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;

/**
 * A {@link SourceVisitor} that performs assignment and pseudo-assignment
 * checking, method invocation checking, and assignability checking.
 *
 * 

* * This implementation uses the {@link AnnotatedTypeFactory} implementation * provided by an associated {@link BaseTypeChecker}; its visitor methods will * invoke this factory on parts of the AST to determine the "annotated type" of * an expression. Then, the visitor methods will check the types in assignments * and pseudo-assignments using {@link #commonAssignmentCheck}, which ultimately * calls the {@link TypeHierarchy#isSubtype} method and reports errors that * violate Java's rules of assignment. * *

* * Note that since this implementation only performs assignment and * pseudo-assignment checking, other rules for custom type systems must be added * in subclasses (e.g., dereference checking in the {@link org.checkerframework.checker.nullness.NullnessChecker} is * implemented in the {@link org.checkerframework.checker.nullness.NullnessChecker}'s * {@link TreeScanner#visitMemberSelect} method). * *

* * This implementation does the following checks: * 1. Assignment and Pseudo-Assignment Check: * It verifies that any assignment type-checks, using * {@code TypeHierarchy.isSubtype} method. This includes method invocation and * method overriding checks. * * 2. Type Validity Check: * It verifies that any user-supplied type is a valid type, using * {@code isValidUse} method. * * 3. (Re-)Assignability Check: * It verifies that any assignment is valid, using * {@code Checker.isAssignable} method. * * @see "JLS $4" * @see TypeHierarchy#isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror) * @see AnnotatedTypeFactory */ /* * Note how the handling of VisitorState is duplicated in AbstractFlow. In * particular, the handling of the assignment context has to be done correctly * in both classes. This is a pain and we should see how to handle this in the * DFF version. TODO: missing assignment context: - array initializer * expressions should have the component type as context */ public class BaseTypeVisitor> extends SourceVisitor { /** The {@link BaseTypeChecker} for error reporting. */ protected final BaseTypeChecker checker; /** The factory to use for obtaining "parsed" version of annotations. */ protected final Factory atypeFactory; /** For obtaining line numbers in -Ashowchecks debugging output. */ protected final SourcePositions positions; /** For storing visitor state. **/ protected final VisitorState visitorState; /** An instance of the {@link ContractsUtils} helper class. */ protected final ContractsUtils contractsUtils; protected static final Pattern thisPattern = Pattern.compile("^(this)$"); /** * @param checker * the type-checker associated with this visitor (for callbacks to * {@link TypeHierarchy#isSubtype}) */ public BaseTypeVisitor(BaseTypeChecker checker) { super(checker); this.checker = checker; this.atypeFactory = createTypeFactory(); this.contractsUtils = ContractsUtils.getInstance(atypeFactory); this.positions = trees.getSourcePositions(); this.visitorState = atypeFactory.getVisitorState(); this.typeValidator = createTypeValidator(); this.vectorType = atypeFactory.fromElement(elements.getTypeElement("java.util.Vector")); } protected BaseTypeVisitor(BaseTypeChecker checker, Factory typeFactory) { super(checker); this.checker = checker; this.atypeFactory = typeFactory; this.contractsUtils = ContractsUtils.getInstance(atypeFactory); this.positions = trees.getSourcePositions(); this.visitorState = atypeFactory.getVisitorState(); this.typeValidator = createTypeValidator(); this.vectorType = atypeFactory.fromElement(elements.getTypeElement("java.util.Vector")); } /** * Constructs an instance of the appropriate type factory for the * implemented type system. * * The default implementation uses the checker naming convention to create * the appropriate type factory. If no factory is found, it returns * {@link BaseAnnotatedTypeFactory}. It reflectively invokes the * constructor that accepts this checker and compilation unit tree * (in that order) as arguments. * * Subclasses have to override this method to create the appropriate * visitor if they do not follow the checker naming convention. * * @return the appropriate type factory */ @SuppressWarnings("unchecked") // unchecked cast to type variable protected Factory createTypeFactory() { // Try to reflectively load the type factory. Class checkerClass = checker.getClass(); while (checkerClass != BaseTypeChecker.class) { final String classToLoad = checkerClass.getName().replace("Checker", "AnnotatedTypeFactory") .replace("Subchecker", "AnnotatedTypeFactory"); AnnotatedTypeFactory result = BaseTypeChecker.invokeConstructorFor(classToLoad, new Class[] { BaseTypeChecker.class }, new Object[] { checker }); if (result != null) { return (Factory) result; } checkerClass = checkerClass.getSuperclass(); } return (Factory) new BaseAnnotatedTypeFactory(checker); } public final Factory getTypeFactory() { return atypeFactory; } // ********************************************************************** // Responsible for updating the factory for the location (for performance) // ********************************************************************** @Override public void setRoot(CompilationUnitTree root) { atypeFactory.setRoot(root); super.setRoot(root); } @Override public Void scan(Tree tree, Void p) { if (tree != null && getCurrentPath() != null) { this.visitorState.setPath(new TreePath(getCurrentPath(), tree)); } return super.scan(tree, p); } @Override public Void visitClass(ClassTree node, Void p) { if (checker.shouldSkipDefs(node)) { // Not "return super.visitClass(node, p);" because that would // recursively call visitors on subtrees; we want to skip the // class entirely. return null; } AnnotatedDeclaredType preACT = visitorState.getClassType(); ClassTree preCT = visitorState.getClassTree(); AnnotatedDeclaredType preAMT = visitorState.getMethodReceiver(); MethodTree preMT = visitorState.getMethodTree(); Pair preAssCtxt = visitorState.getAssignmentContext(); // For flow-sensitive type checking, it's significant that we get the // annotated type of the ClassTree before checking the type of any // code within the class. The call below causes flow analysis to // be run over the class. See GenericAnnotatedTypeFactory // .annotateImplicitWithFlow where analysis is performed. visitorState.setClassType(atypeFactory.getAnnotatedType(node)); visitorState.setClassTree(node); visitorState.setMethodReceiver(null); visitorState.setMethodTree(null); visitorState.setAssignmentContext(null); try { if (!TreeUtils.hasExplicitConstructor(node)) { checkDefaultConstructor(node); } /* Visit the extends and implements clauses. * The superclass also visits them, but only calls visitParameterizedType, which * looses a main modifier. */ Tree ext = node.getExtendsClause(); if (ext != null) { validateTypeOf(ext); } List impls = node.getImplementsClause(); if (impls != null) { for (Tree im : impls) { validateTypeOf(im); } } return super.visitClass(node, p); } finally { this.visitorState.setClassType(preACT); this.visitorState.setClassTree(preCT); this.visitorState.setMethodReceiver(preAMT); this.visitorState.setMethodTree(preMT); this.visitorState.setAssignmentContext(preAssCtxt); } } protected void checkDefaultConstructor(ClassTree node) { } private static boolean isFieldIsExpressionQualifier(AnnotationMirror anno) { return ((TypeElement)anno.getAnnotationType().asElement()).getAnnotation(FieldIsExpression.class) != null; } /* * Verifies that annotations relevant to the current checker on formal parameters * do not use formal parameter names as expressions. Issues a warning if they do. * * Returns the list of formal parameter names. * Returns null if the method has no formal parameters. */ private List verifyParameterAnnotationsForParameterNames(MethodTree node) { List parameters = node.getParameters(); if (parameters != null && !parameters.isEmpty()) { ArrayList formalParamNames = new ArrayList(); for (VariableTree param : parameters) { formalParamNames.add(param.getName().toString()); } for (VariableTree param : parameters) { AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(param); if (atm != null) { Set annotationMirrors = atm.getAnnotations(); if (annotationMirrors != null) { for (AnnotationMirror anno : annotationMirrors) { if (isFieldIsExpressionQualifier(anno) && atypeFactory.isSupportedQualifier(anno)) { List expressions = AnnotationUtils.getElementValueArray(anno, "value", String.class, false); for (String expression : expressions) { if (formalParamNames.contains(expression)) { checker.report(Result.warning("method.declaration.expression.parameter.name", param.getName().toString(), node.getName().toString(), expression, formalParamNames.indexOf(expression) + 1, expression), node); } } } } } } } return formalParamNames; } return null; } /** * Performs pseudo-assignment check: checks that the method obeys override * and subtype rules to all overridden methods. * * The override rule specifies that a method, m1, may override a method * m2 only if: *

    *
  • m1 return type is a subtype of m2
  • *
  • m1 receiver type is a supertype of m2
  • *
  • m1 parameters are supertypes of corresponding m2 parameters
  • *
* * Also, it issues a "missing.this" error for static method annotated * receivers. */ @Override public Void visitMethod(MethodTree node, Void p) { // We copy the result from getAnnotatedType to ensure that // circular types (e.g. K extends Comparable) are represented // by circular AnnotatedTypeMirrors, which avoids problems with // later checks. // TODO: Find a cleaner way to ensure circular AnnotatedTypeMirrors. AnnotatedExecutableType methodType = atypeFactory.getAnnotatedType(node).deepCopy(); AnnotatedDeclaredType preMRT = visitorState.getMethodReceiver(); MethodTree preMT = visitorState.getMethodTree(); visitorState.setMethodReceiver(methodType.getReceiverType()); visitorState.setMethodTree(node); ExecutableElement methodElement = TreeUtils.elementFromDeclaration(node); try { if (InternalUtils.isAnonymousConstructor(node)) { // We shouldn't dig deeper return null; } // check method purity if needed { boolean anyPurityAnnotation = PurityUtils.hasPurityAnnotation( atypeFactory, node); boolean checkPurityAlways = checker.hasOption("suggestPureMethods"); boolean checkPurityAnnotations = checker.hasOption("checkPurityAnnotations"); if (checkPurityAnnotations && (anyPurityAnnotation || checkPurityAlways)) { // check "no" purity List kinds = PurityUtils.getPurityKinds(atypeFactory, node); // @Deterministic makes no sense for a void method or constructor boolean isDeterministic = kinds.contains(Pure.Kind.DETERMINISTIC); if (isDeterministic) { if (TreeUtils.isConstructor(node)) { checker.report(Result.warning("purity.deterministic.constructor"), node); } else if (InternalUtils.typeOf(node.getReturnType()).getKind() == TypeKind.VOID) { checker.report(Result.warning("purity.deterministic.void.method"), node); } } // Report errors if necessary. PurityResult r = PurityChecker.checkPurity(node.getBody(), atypeFactory, checker.hasOption("assumeSideEffectFree")); if (!r.isPure(kinds)) { reportPurityErrors(r, node, kinds); } // Issue a warning if the method is pure, but not annotated // as such (if the feature is activated). if (checkPurityAlways) { Collection additionalKinds = new HashSet<>( r.getTypes()); additionalKinds.removeAll(kinds); if (TreeUtils.isConstructor(node)) { additionalKinds.remove(Pure.Kind.DETERMINISTIC); } if (!additionalKinds.isEmpty()) { if (additionalKinds.size() == 2) { checker.report( Result.warning("purity.more.pure", node.getName()), node); } else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) { checker.report( Result.warning("purity.more.sideeffectfree", node.getName()), node); } else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) { checker.report( Result.warning("purity.more.deterministic", node.getName()), node); } else { assert false : "BaseTypeVisitor reached undesirable state"; } } } } } // Passing the whole method/constructor validates the return type validateTypeOf(node); // Validate types in throws clauses for (ExpressionTree thr : node.getThrows()) { validateTypeOf(thr); } AnnotatedDeclaredType enclosingType = (AnnotatedDeclaredType) atypeFactory .getAnnotatedType(methodElement.getEnclosingElement()); // Find which method this overrides! Map overriddenMethods = AnnotatedTypes .overriddenMethods(elements, atypeFactory, methodElement); for (Map.Entry pair : overriddenMethods .entrySet()) { AnnotatedDeclaredType overriddenType = pair.getKey(); AnnotatedExecutableType overriddenMethod = AnnotatedTypes .asMemberOf(types, atypeFactory, overriddenType, pair.getValue()); if (!checkOverride(node, enclosingType, overriddenMethod, overriddenType, p)) { // Stop at the first mismatch; this makes a difference only if // -Awarns is passed, in which case multiple warnings might be raised on // the same method, not adding any value. See Issue 373. break; } } return super.visitMethod(node, p); } finally { boolean abstractMethod = methodElement.getModifiers().contains(Modifier.ABSTRACT) || methodElement.getModifiers().contains(Modifier.NATIVE); if (!abstractMethod) { // check postcondition annotations checkPostconditions(node, methodElement); // check conditional method postcondition checkConditionalPostconditions(node, methodElement); } // check well-formedness of pre/postcondition List formalParamNames = verifyParameterAnnotationsForParameterNames(node); checkPreconditionsConsistency(node, methodElement, formalParamNames); checkPostconditionsConsistency(node, methodElement, formalParamNames); checkConditionalPostconditionsConsistency(node, methodElement, formalParamNames); visitorState.setMethodReceiver(preMRT); visitorState.setMethodTree(preMT); } } /** * Reports errors found during purity checking. */ protected void reportPurityErrors(PurityResult result, MethodTree node, Collection expectedTypes) { assert !result.isPure(expectedTypes); Collection t = EnumSet.copyOf(expectedTypes); t.removeAll(result.getTypes()); if (t.contains(Pure.Kind.DETERMINISTIC) || t.contains(Pure.Kind.SIDE_EFFECT_FREE)) { String msgPrefix = "purity.not.deterministic.not.sideeffectfree."; if (!t.contains(Pure.Kind.SIDE_EFFECT_FREE)) { msgPrefix = "purity.not.deterministic."; } else if (!t.contains(Pure.Kind.DETERMINISTIC)) { msgPrefix = "purity.not.sideeffectfree."; } for (Pair r: result.getNotBothReasons()) { @SuppressWarnings("CompilerMessages") /*@CompilerMessageKey*/ String msg = msgPrefix + r.second; checker.report(Result.failure(msg), r.first); } if (t.contains(Pure.Kind.SIDE_EFFECT_FREE)) { for (Pair r: result.getNotSeFreeReasons()) { @SuppressWarnings("CompilerMessages") /*@CompilerMessageKey*/ String msg = "purity.not.sideeffectfree." + r.second; checker.report(Result.failure(msg), r.first); } } if (t.contains(Pure.Kind.DETERMINISTIC)) { for (Pair r: result.getNotDetReasons()) { @SuppressWarnings("CompilerMessages") /*@CompilerMessageKey*/ String msg = "purity.not.deterministic." + r.second; checker.report(Result.failure(msg), r.first); } } } } /** * Checks all (non-conditional) postcondition on the method {@code node} * with element {@code methodElement}. */ protected void checkPostconditions(MethodTree node, ExecutableElement methodElement) { FlowExpressionContext flowExprContext = null; Set> postconditions = contractsUtils .getPostconditions(methodElement); for (Pair p : postconditions) { String expression = p.first; AnnotationMirror annotation = AnnotationUtils.fromName(elements, p.second); // Only check if the postcondition concerns this checker if (!atypeFactory.isSupportedQualifier(annotation)) { continue; } if (flowExprContext == null) { flowExprContext = FlowExpressionParseUtil .buildFlowExprContextForDeclaration(node, getCurrentPath(), checker.getContext()); } FlowExpressions.Receiver expr = null; try { // TODO: currently, these expressions are parsed at the // declaration (i.e. here) and for every use. this could be // optimized to store the result the first time. (same for // other annotations) expr = FlowExpressionParseUtil.parse(expression, flowExprContext, getCurrentPath()); CFAbstractStore exitStore = atypeFactory .getRegularExitStore(node); if (exitStore == null) { // if there is no regular exitStore, then the method // cannot reach the regular exit and there is no need to // check anything } else { CFAbstractValue value = exitStore.getValue(expr); AnnotationMirror inferredAnno = value == null ? null : value.getType().getAnnotationInHierarchy( annotation); if (!checkContract(expr, annotation, inferredAnno, exitStore)) { checker.report( Result.failure("contracts.postcondition.not.satisfied", expr.toString()), node); } } } catch (FlowExpressionParseException e) { // report errors here checker.report(e.getResult(), node); } } } /** * Checks all (non-conditional) postcondition on the method {@code node} * with element {@code methodElement} for consistency, i.e. * that no formal parameter names are mentioned in the postconditions * (an index such as "#1" should be used instead), and that all * formal parameters referred to by an index in the postconditions are * effectively final. */ protected void checkPostconditionsConsistency(MethodTree node, ExecutableElement methodElement, List formalParamNames) { FlowExpressionContext flowExprContext = null; Set> postconditions = contractsUtils .getPostconditions(methodElement); for (Pair p : postconditions) { String expression = p.first; AnnotationMirror annotation = AnnotationUtils.fromName(elements, p.second); if (flowExprContext == null) { flowExprContext = FlowExpressionParseUtil .buildFlowExprContextForDeclaration(node, getCurrentPath(), checker.getContext()); } // Only check if the postcondition concerns this checker if (!atypeFactory.isSupportedQualifier(annotation)) { continue; } if (formalParamNames != null && formalParamNames.contains(expression)) { checker.report(Result.warning("contracts.postcondition.expression.parameter.name", node.getName().toString(), expression, formalParamNames.indexOf(expression) + 1, expression), node); } try { FlowExpressionParseUtil.parse(expression, flowExprContext, getCurrentPath()); } catch (FlowExpressionParseException e) { // ignore expressions that do not parse continue; } checkFlowExprParameters(methodElement, expression); } } /** * Checks all conditional postcondition on the method {@code node} with * element {@code methodElement}. */ protected void checkConditionalPostconditions(MethodTree node, ExecutableElement methodElement) { FlowExpressionContext flowExprContext = null; Set>> conditionalPostconditions = contractsUtils .getConditionalPostconditions(methodElement); for (Pair> p : conditionalPostconditions) { String expression = p.first; boolean result = p.second.first; AnnotationMirror annotation = AnnotationUtils.fromName(elements, p.second.second); // Only check if the postcondition concerns this checker if (!atypeFactory.isSupportedQualifier(annotation)) { continue; } if (flowExprContext == null) { flowExprContext = FlowExpressionParseUtil .buildFlowExprContextForDeclaration(node, getCurrentPath(), checker.getContext()); } FlowExpressions.Receiver expr = null; try { // TODO: currently, these expressions are parsed at the // declaration (i.e. here) and for every use. this could be // optimized to store the result the first time. (same for // other annotations) expr = FlowExpressionParseUtil.parse(expression, flowExprContext, getCurrentPath()); // check return type of method boolean booleanReturnType = TypesUtils.isBooleanType(InternalUtils.typeOf(node.getReturnType())); if (!booleanReturnType) { checker.report( Result.failure("contracts.conditional.postcondition.invalid.returntype"), node); // No reason to go ahead with further checking. The // annotation is invalid. continue; } List returnStatements = atypeFactory.getReturnStatementStores(node); for (Object rt : returnStatements) { @SuppressWarnings("unchecked") Pair, ? extends CFAbstractStore>> r = (Pair, ? extends CFAbstractStore>>) rt; ReturnNode returnStmt = r.first; if (r.second == null) { // Unreachable return statements have no stores, but // there // is no need to check them. continue; } Node retValNode = returnStmt.getResult(); Boolean retVal = retValNode instanceof BooleanLiteralNode ? ((BooleanLiteralNode) retValNode) .getValue() : null; CFAbstractStore exitStore; if (result) { exitStore = r.second.getThenStore(); } else { exitStore = r.second.getElseStore(); } CFAbstractValue value = exitStore.getValue(expr); // don't check if return statement certainly does not // match 'result'. at the moment, this means the result // is a boolean literal if (retVal == null || retVal == result) { AnnotationMirror inferredAnno = value == null ? null : value.getType().getAnnotationInHierarchy( annotation); if (!checkContract(expr, annotation, inferredAnno, exitStore)) { checker.report( Result.failure("contracts.conditional.postcondition.not.satisfied", expr.toString()), returnStmt.getTree()); } } } } catch (FlowExpressionParseException e) { // report errors here checker.report(e.getResult(), node); } } } /** * Checks all conditional postcondition on the method with element * {@code methodElement} for consistency, i.e. that no formal parameter * names are mentioned in the conditional postconditions (an index such * as "#1" should be used instead), and that all formal parameters * referred to by an index in the conditional postconditions are * effectively final. */ protected void checkConditionalPostconditionsConsistency(MethodTree node, ExecutableElement methodElement, List formalParamNames) { FlowExpressionContext flowExprContext = null; Set>> conditionalPostconditions = contractsUtils .getConditionalPostconditions(methodElement); for (Pair> p : conditionalPostconditions) { String expression = p.first; AnnotationMirror annotation = AnnotationUtils.fromName(elements, p.second.second); if (flowExprContext == null) { flowExprContext = FlowExpressionParseUtil .buildFlowExprContextForDeclaration(node, getCurrentPath(), checker.getContext()); } // Only check if the postcondition concerns this checker if (!atypeFactory.isSupportedQualifier(annotation)) { continue; } if (formalParamNames != null && formalParamNames.contains(expression)) { checker.report(Result.warning("contracts.conditional.postcondition.expression.parameter.name", node.getName().toString(), expression, formalParamNames.indexOf(expression) + 1, expression), node); } try { FlowExpressionParseUtil.parse(expression, flowExprContext, getCurrentPath()); } catch (FlowExpressionParseException e) { // ignore expressions that do not parse continue; } checkFlowExprParameters(methodElement, expression); } } /** * Check that the parameters used in {@code stringExpr} are effectively final for method * {@code method}. */ protected void checkFlowExprParameters(ExecutableElement method, String stringExpr) { // check that all parameters used in the expression are // effectively final, so that they cannot be modified List parameterIndices = FlowExpressionParseUtil.parameterIndices(stringExpr); for (Integer idx : parameterIndices) { VariableElement parameter = method.getParameters().get(idx - 1); if (!ElementUtils.isEffectivelyFinal(parameter)) { checker.report( Result.failure("flowexpr.parameter.not.final", "#" + idx, stringExpr), method); } } } @Override public Void visitTypeParameter(TypeParameterTree node, Void p) { validateTypeOf(node); // Check the bounds here and not with every TypeParameterTree. // For the latter, we only need to check annotations on the type variable itself. // Why isn't this covered by the super call? for (Tree tpb : node.getBounds()) { validateTypeOf(tpb); } return super.visitTypeParameter(node, p); } // ********************************************************************** // Assignment checkers and pseudo-assignments // ********************************************************************** @Override public Void visitVariable(VariableTree node, Void p) { Pair preAssCtxt = visitorState.getAssignmentContext(); visitorState.setAssignmentContext(Pair.of((Tree) node, atypeFactory.getAnnotatedType(node))); try { // If there's no assignment in this variable declaration, skip it. if (node.getInitializer() != null) { commonAssignmentCheck(node, node.getInitializer(), "assignment.type.incompatible"); } else { // commonAssignmentCheck validates the type of node, // so only validate if commonAssignmentCheck wasn't called validateTypeOf(node); } return super.visitVariable(node, p); } finally { visitorState.setAssignmentContext(preAssCtxt); } } /** * Performs two checks: subtyping and assignability checks, using * {@link #commonAssignmentCheck(Tree, ExpressionTree, String)}. * * If the subtype check fails, it issues a "assignment.type.incompatible" error. */ @Override public Void visitAssignment(AssignmentTree node, Void p) { Pair preAssCtxt = visitorState.getAssignmentContext(); visitorState.setAssignmentContext(Pair.of((Tree) node.getVariable(), atypeFactory.getAnnotatedType(node.getVariable()))); try { commonAssignmentCheck(node.getVariable(), node.getExpression(), "assignment.type.incompatible"); return super.visitAssignment(node, p); } finally { visitorState.setAssignmentContext(preAssCtxt); } } /** * Performs a subtype check, to test whether the node expression * iterable type is a subtype of the variable type in the enhanced for * loop. * * If the subtype check fails, it issues a "enhancedfor.type.incompatible" error. */ @Override public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void p) { AnnotatedTypeMirror var = atypeFactory.getAnnotatedType(node.getVariable()); AnnotatedTypeMirror iterableType = atypeFactory.getAnnotatedType(node.getExpression()); AnnotatedTypeMirror iteratedType = AnnotatedTypes.getIteratedType(checker.getProcessingEnvironment(), atypeFactory, iterableType); boolean valid = validateTypeOf(node.getVariable()); if (valid) { commonAssignmentCheck(var, iteratedType, node.getExpression(), "enhancedfor.type.incompatible"); } return super.visitEnhancedForLoop(node, p); } /** * Performs a method invocation check. * * An invocation of a method, m, on the receiver, r is valid only if: *
    *
  • passed arguments are subtypes of corresponding m parameters
  • *
  • r is a subtype of m receiver type
  • *
  • if m is generic, passed type arguments are subtypes * of m type variables
  • *
*/ @Override public Void visitMethodInvocation(MethodInvocationTree node, Void p) { // Skip calls to the Enum constructor (they're generated by javac and // hard to check), also see CFGBuilder.visitMethodInvocation. if (TreeUtils.isEnumSuper(node)) { return super.visitMethodInvocation(node, p); } if (shouldSkipUses(node)) { return super.visitMethodInvocation(node, p); } Pair> mfuPair = atypeFactory.methodFromUse(node); AnnotatedExecutableType invokedMethod = mfuPair.first; List typeargs = mfuPair.second; List paramBounds = new ArrayList<>(); for (AnnotatedTypeVariable param : invokedMethod.getTypeVariables()) { paramBounds.add(param.getBounds()); } checkTypeArguments(node, paramBounds, typeargs, node.getTypeArguments()); List params = AnnotatedTypes.expandVarArgs(atypeFactory, invokedMethod, node.getArguments()); checkArguments(params, node.getArguments()); if (isVectorCopyInto(invokedMethod)) { typeCheckVectorCopyIntoArgument(node, params); } ExecutableElement invokedMethodElement = invokedMethod.getElement(); if (!ElementUtils.isStatic(invokedMethodElement) && !TreeUtils.isSuperCall(node)) { checkMethodInvocability(invokedMethod, node); } // check precondition annotations checkPreconditions(node, contractsUtils.getPreconditions(invokedMethodElement)); // Do not call super, as that would observe the arguments without // a set assignment context. scan(node.getMethodSelect(), p); return null; // super.visitMethodInvocation(node, p); } /** * Checks that all the given {@code preconditions} hold true immediately prior to * the method invocation or variable access at {@code tree}. * * @param tree the Tree immediately prior to which the preconditions must hold true * @param preconditions the preconditions to be checked */ protected void checkPreconditions(Tree tree, Set> preconditions) { // This check is needed for the GUI effects and Units Checkers tests to pass. // TODO: Remove this check and investigate the root cause. if (preconditions.isEmpty()) { return; } checkPreconditions(tree, atypeFactory.getNodeForTree(tree), preconditions); } /** * Checks that all the given {@code preconditions} hold true immediately prior to * the method invocation or variable access at {@code node}. Errors are reported * with respect to {@code treeForErrorReporting}, which does not need to correspond to {@code node}. * * @param treeForErrorReporting the Tree used to report the error via checker.report. * @param node the Node immediately prior to which the preconditions must hold true * @param preconditions the preconditions to be checked */ protected void checkPreconditions(Tree treeForErrorReporting, Node node, Set> preconditions) { if (preconditions.isEmpty()) { return; } FlowExpressionContext flowExprContext = getFlowExpressionContextFromNode(node); if (flowExprContext == null) { checker.report(Result.failure("flowexpr.parse.context.not.determined", node), treeForErrorReporting); return; } for (Pair p : preconditions) { String expression = p.first; AnnotationMirror anno = AnnotationUtils.fromName(elements, p.second); // Only check the precondition if it concerns this checker if (!atypeFactory.isSupportedQualifier(anno)) { continue; } try { FlowExpressions.Receiver expr = parseExpressionString(expression, flowExprContext, getCurrentPath(), node, treeForErrorReporting); CFAbstractStore store = atypeFactory.getStoreBefore(node); CFAbstractValue value = store.getValue(expr); AnnotationMirror inferredAnno = null; if (value != null) { inferredAnno = value.getType().getAnnotationInHierarchy(anno); } if (!checkContract(expr, anno, inferredAnno, store)) { checker.report(Result.failure( treeForErrorReporting.getKind() == Tree.Kind.METHOD_INVOCATION ? "contracts.precondition.not.satisfied" : "contracts.precondition.not.satisfied.field", treeForErrorReporting.toString(), expr == null ? expression : expr.toString()), treeForErrorReporting); } } catch (FlowExpressionParseException e) { // errors are reported at declaration site } } } /** * Returns a flow expression context corresponding to the given {@code node}. * Only handles the kinds of Nodes for which a precondition check is applicable * and for which values are stored in {@link CFAbstractStore}. Returns null * if the Node kind is not handled. * * @param node the Node to generate the flow expression context for * @return the resulting flow expression context, or null if the Node kind is not handled. */ private FlowExpressionContext getFlowExpressionContextFromNode(Node node) { FlowExpressionContext flowExprContext = null; if (node instanceof MethodInvocationNode) { flowExprContext = FlowExpressionParseUtil .buildFlowExprContextForUse( (MethodInvocationNode) node, checker.getContext()); } else if (node instanceof FieldAccessNode) { // Adapted from FlowExpressionParseUtil.buildFlowExprContextForUse Receiver internalReceiver = FlowExpressions.internalReprOf(atypeFactory, ((FieldAccessNode) node).getReceiver()); flowExprContext = new FlowExpressionContext( internalReceiver, null, checker.getContext()); } else if (node instanceof LocalVariableNode) { // Adapted from org.checkerframework.dataflow.cfg.CFGBuilder.CFGTranslationPhaseOne.visitVariable ClassTree enclosingClass = TreeUtils.enclosingClass(getCurrentPath()); TypeElement classElem = TreeUtils.elementFromDeclaration(enclosingClass); Node receiver = new ImplicitThisLiteralNode(classElem.asType()); Receiver internalReceiver = FlowExpressions.internalReprOf(atypeFactory, receiver); flowExprContext = new FlowExpressionContext( internalReceiver, null, checker.getContext()); } else if (node instanceof ArrayAccessNode) { // Adapted from FlowExpressionParseUtil.buildFlowExprContextForUse Receiver internalReceiver = FlowExpressions.internalReprOfArrayAccess(atypeFactory, (ArrayAccessNode) node); flowExprContext = new FlowExpressionContext( internalReceiver, null, checker.getContext()); } else if (node instanceof ExplicitThisLiteralNode || node instanceof ImplicitThisLiteralNode || node instanceof ThisLiteralNode) { Receiver internalReceiver = FlowExpressions.internalReprOf(atypeFactory, node, false); flowExprContext = new FlowExpressionContext( internalReceiver, null, checker.getContext()); } return flowExprContext; } /*** * Returns the flow expression receiver for the {@code expression} given the * {@code flowExprContext}. The expression "this" is allowed and is handled. * {@code node} refers to the method invocation or variable access being analyzed. * It can be used by an overriding method for special handling of expressions * such as "itself" which may indicate a reference to {@code node}. * * @param expression the flow expression string to be parsed * @param flowExprContext the flow expression context with respect to which the expression string is to be evaluated * @param node the Node immediately prior to which the preconditions checked by the calling method must hold true. * Used by overriding implementations. Allowed to be null. * @param path the TreePath from which to obtain the scope relative to which local variables are parsed * @param treeForErrorReporting the Tree used to report parsing errors via checker.report. * Used by overriding implementations. */ protected FlowExpressions.Receiver parseExpressionString(String expression, FlowExpressionContext flowExprContext, TreePath path, Node node, Tree treeForErrorReporting) throws FlowExpressionParseException { expression = expression.trim(); Matcher selfMatcher = thisPattern.matcher(expression); if (selfMatcher.matches()) { // It is possible that expression == "this" after this call. expression = flowExprContext.receiver.toString().trim(); } return FlowExpressionParseUtil.parse(expression, flowExprContext, path); } /** * Checks all the preconditions of the method with element * {@code methodElement} for consistency, i.e. that no formal * parameter names are mentioned in the preconditions * (an index such as "#1" should be used instead), and that all * formal parameters referred to by an index in the preconditions are * effectively final. */ protected void checkPreconditionsConsistency(MethodTree node, ExecutableElement methodElement, List formalParamNames) { FlowExpressionContext flowExprContext = null; Set> preconditions = contractsUtils .getPreconditions(methodElement); for (Pair p : preconditions) { String expression = p.first; AnnotationMirror anno = AnnotationUtils .fromName(elements, p.second); if (flowExprContext == null) { flowExprContext = FlowExpressionParseUtil .buildFlowExprContextForDeclaration(node, getCurrentPath(), checker.getContext()); } // Only check if the precondition concerns this checker if (!atypeFactory.isSupportedQualifier(anno)) { return; } if (formalParamNames != null && formalParamNames.contains(expression)) { checker.report(Result.warning("contracts.precondition.expression.parameter.name", node.getName().toString(), expression, formalParamNames.indexOf(expression) + 1, expression), node); } try { FlowExpressionParseUtil.parse(expression, flowExprContext, getCurrentPath()); } catch (FlowExpressionParseException e) { // ignore expressions that do not parse continue; } checkFlowExprParameters(methodElement, expression); } } /** * Returns true if and only if {@code inferredAnnotation} is valid for a * given expression to match the {@code necessaryAnnotation}. * *

* By default, {@code inferredAnnotation} must be a subtype of * {@code necessaryAnnotation}, but subclasses might override this behavior. */ protected boolean checkContract(Receiver expr, AnnotationMirror necessaryAnnotation, AnnotationMirror inferredAnnotation, CFAbstractStore store) { return inferredAnnotation != null && atypeFactory.getQualifierHierarchy().isSubtype(inferredAnnotation, necessaryAnnotation); } // Handle case Vector.copyInto() private final AnnotatedDeclaredType vectorType; /** * Returns true if the method symbol represents {@code Vector.copyInto} */ protected boolean isVectorCopyInto(AnnotatedExecutableType method) { ExecutableElement elt = method.getElement(); if (elt.getSimpleName().contentEquals("copyInto") && elt.getParameters().size() == 1) return true; return false; } /** * Type checks the method arguments of {@code Vector.copyInto()}. * * The Checker Framework special-cases the method invocation, as it is * type safety cannot be expressed by Java's type system. * * For a Vector {@code v} of type {@code Vectory}, the method * invocation {@code v.copyInto(arr)} is type-safe iff {@code arr} * is a array of type {@code T[]}, where {@code T} is a subtype of * {@code E}. * * In other words, this method checks that the type argument of the * receiver method is a subtype of the component type of the passed array * argument. * * @param node a method invocation of {@code Vector.copyInto()} * @param params the types of the parameters of {@code Vectory.copyInto()} * */ protected void typeCheckVectorCopyIntoArgument(MethodInvocationTree node, List params) { assert params.size() == 1 : "invalid no. of parameters " + params + " found for method invocation " + node; assert node.getArguments().size() == 1 : "invalid no. of arguments in method invocation " + node; AnnotatedTypeMirror passed = atypeFactory.getAnnotatedType(node.getArguments().get(0)); AnnotatedArrayType passedAsArray = (AnnotatedArrayType)passed; AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(node); AnnotatedDeclaredType receiverAsVector = (AnnotatedDeclaredType) AnnotatedTypes .asSuper(checker.getProcessingEnvironment().getTypeUtils(), atypeFactory, receiver, vectorType); if (receiverAsVector == null || receiverAsVector.getTypeArguments().isEmpty()) return; commonAssignmentCheck( passedAsArray.getComponentType(), receiverAsVector.getTypeArguments().get(0), node.getArguments().get(0), "vector.copyinto.type.incompatible"); } /** * Performs a new class invocation check. * * An invocation of a constructor, c, is valid only if: *

    *
  • passed arguments are subtypes of corresponding c parameters
  • *
  • if c is generic, passed type arguments are subtypes * of c type variables
  • *
*/ @Override public Void visitNewClass(NewClassTree node, Void p) { if (checker.shouldSkipUses(InternalUtils.constructor(node))) { return super.visitNewClass(node, p); } Pair> fromUse = atypeFactory.constructorFromUse(node); AnnotatedExecutableType constructor = fromUse.first; List typeargs = fromUse.second; List passedArguments = node.getArguments(); List params = AnnotatedTypes.expandVarArgs(atypeFactory, constructor, passedArguments); checkArguments(params, passedArguments); List paramBounds = new ArrayList<>(); for (AnnotatedTypeVariable param : constructor.getTypeVariables()) { paramBounds.add(param.getBounds()); } checkTypeArguments(node, paramBounds, typeargs, node.getTypeArguments()); boolean valid = validateTypeOf(node); if (valid) { AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(node); checkConstructorInvocation(dt, constructor, node); } return super.visitNewClass(node, p); } @Override public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { Pair result = atypeFactory.getFnInterfaceFromTree(node); AnnotatedExecutableType overridden = result.second; if (node.getBody().getKind() != Tree.Kind.BLOCK) { // Check return type for single statement returns here AnnotatedTypeMirror ret = overridden.getReturnType(); if (ret.getKind() != TypeKind.VOID) { visitorState.setAssignmentContext(Pair.of((Tree) node, ret)); commonAssignmentCheck(ret, (ExpressionTree) node.getBody(), "return.type.incompatible"); } } // Check parameters for (int i = 0; i < overridden.getParameterTypes().size(); ++i) { AnnotatedTypeMirror overridingParm = atypeFactory.getAnnotatedType(node.getParameters().get(i)); commonAssignmentCheck(overridingParm, overridden.getParameterTypes().get(i), node.getParameters().get(i), "lambda.param.type.incompatible"); } // TODO: Post conditions? return super.visitLambdaExpression(node, p); } @Override public Void visitMemberReference(MemberReferenceTree node, Void p) { this.checkMethodReferenceAsOverride(node, p); return super.visitMemberReference(node, p); } /** * Checks that the type of the return expression is a subtype of the * enclosing method required return type. If not, it issues a * "return.type.incompatible" error. */ @Override public Void visitReturn(ReturnTree node, Void p) { // Don't try to check return expressions for void methods. if (node.getExpression() == null) { return super.visitReturn(node, p); } Pair preAssCtxt = visitorState.getAssignmentContext(); try { Tree enclosing = TreeUtils.enclosingOfKind(getCurrentPath(), new HashSet(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION))); AnnotatedTypeMirror ret = null; if (enclosing.getKind() == Tree.Kind.METHOD) { MethodTree enclosingMethod = TreeUtils.enclosingMethod(getCurrentPath()); boolean valid = validateTypeOf(enclosing); if (valid) { ret = atypeFactory.getMethodReturnType(enclosingMethod, node); } } else { Pair result = atypeFactory.getFnInterfaceFromTree((LambdaExpressionTree) enclosing); ret = result.second.getReturnType(); } if (ret != null) { visitorState.setAssignmentContext(Pair.of((Tree) node, ret)); commonAssignmentCheck(ret, node.getExpression(), "return.type.incompatible"); } return super.visitReturn(node, p); } finally { visitorState.setAssignmentContext(preAssCtxt); } } /** TODO: something similar to visitReturn should be done. public Void visitThrow(ThrowTree node, Void p) { return super.visitThrow(node, p); } */ /** * Ensure that the annotation arguments comply to their declarations. This * needs some special casing, as annotation arguments form special trees. */ @Override public Void visitAnnotation(AnnotationTree node, Void p) { List args = node.getArguments(); if (args.isEmpty()) { // Nothing to do if there are no annotation arguments. return null; } Element anno = TreeInfo.symbol((JCTree) node.getAnnotationType()); if (anno.toString().equals(DefaultQualifier.class.getName()) || anno.toString().equals(SuppressWarnings.class.getName())) { // Skip these two annotations, as we don't care about the // arguments to them. return null; } // Mapping from argument simple name to its annotated type. Map annoTypes = new HashMap(); for (Element encl : ElementFilter.methodsIn(anno.getEnclosedElements())) { AnnotatedExecutableType exeatm = (AnnotatedExecutableType) atypeFactory.getAnnotatedType(encl); AnnotatedTypeMirror retty = exeatm.getReturnType(); annoTypes.put(encl.getSimpleName().toString(), retty); } for (ExpressionTree arg : args) { if (!(arg instanceof AssignmentTree)) { // TODO: when can this happen? continue; } AssignmentTree at = (AssignmentTree) arg; // Ensure that we never ask for the annotated type of an annotation, because // we don't have a type for annotations. if (at.getExpression().getKind() == Tree.Kind.ANNOTATION) { visitAnnotation((AnnotationTree) at.getExpression(), p); continue; } if (at.getExpression().getKind() == Tree.Kind.NEW_ARRAY) { NewArrayTree nat = (NewArrayTree) at.getExpression(); boolean isAnno = false; for (ExpressionTree init : nat.getInitializers()) { if (init.getKind() == Tree.Kind.ANNOTATION) { visitAnnotation((AnnotationTree) init, p); isAnno = true; } } if (isAnno) { continue; } } AnnotatedTypeMirror expected = annoTypes.get(at.getVariable().toString()); Pair preAssCtxt = visitorState.getAssignmentContext(); { // Determine and set the new assignment context. ExpressionTree var = at.getVariable(); assert var instanceof IdentifierTree : "Expected IdentifierTree as context. Found: " + var; AnnotatedTypeMirror meth = atypeFactory.getAnnotatedType(var); assert meth instanceof AnnotatedExecutableType : "Expected AnnotatedExecutableType as context. Found: " + meth; AnnotatedTypeMirror newctx = ((AnnotatedExecutableType) meth).getReturnType(); visitorState.setAssignmentContext(Pair.of((Tree) null, newctx)); } try { AnnotatedTypeMirror actual = atypeFactory.getAnnotatedType(at.getExpression()); if (expected.getKind() != TypeKind.ARRAY) { // Expected is not an array -> direct comparison. commonAssignmentCheck(expected, actual, at.getExpression(), "annotation.type.incompatible"); } else { if (actual.getKind() == TypeKind.ARRAY) { // Both actual and expected are arrays. commonAssignmentCheck(expected, actual, at.getExpression(), "annotation.type.incompatible"); } else { // The declaration is an array type, but just a single // element is given. commonAssignmentCheck(((AnnotatedArrayType) expected).getComponentType(), actual, at.getExpression(), "annotation.type.incompatible"); } } } finally { visitorState.setAssignmentContext(preAssCtxt); } } return null; } /** * If the computation of the type of the ConditionalExpressionTree in * org.checkerframework.framework.type.TypeFromTree.TypeFromExpression.visitConditionalExpression(ConditionalExpressionTree, AnnotatedTypeFactory) * is correct, the following checks are redundant. * However, let's add another failsafe guard and do the checks. */ @Override public Void visitConditionalExpression(ConditionalExpressionTree node, Void p) { AnnotatedTypeMirror cond = atypeFactory.getAnnotatedType(node); this.commonAssignmentCheck(cond, node.getTrueExpression(), "conditional.type.incompatible"); this.commonAssignmentCheck(cond, node.getFalseExpression(), "conditional.type.incompatible"); return super.visitConditionalExpression(node, p); } // ********************************************************************** // Check for illegal re-assignment // ********************************************************************** /** * Performs assignability check using * {@link #checkAssignability(AnnotatedTypeMirror, Tree)}. */ @Override public Void visitUnary(UnaryTree node, Void p) { if ((node.getKind() == Tree.Kind.PREFIX_DECREMENT) || (node.getKind() == Tree.Kind.PREFIX_INCREMENT) || (node.getKind() == Tree.Kind.POSTFIX_DECREMENT) || (node.getKind() == Tree.Kind.POSTFIX_INCREMENT)) { AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node.getExpression()); checkAssignability(type, node.getExpression()); } return super.visitUnary(node, p); } /** * Performs assignability check using * {@link #checkAssignability(AnnotatedTypeMirror, Tree)}. */ @Override public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) { // If node is the tree represnting the compounds assignment s += expr, // Then this method should check whether s + expr can be assigned to s, // but the "s + expr" tree does not exist. So instead, check that // s += expr can be assigned to s. commonAssignmentCheck(node.getVariable(), node, "compound.assignment.type.incompatible"); return super.visitCompoundAssignment(node, p); } // ********************************************************************** // Check for invalid types inserted by the user // ********************************************************************** @Override public Void visitNewArray(NewArrayTree node, Void p) { boolean valid = validateTypeOf(node); if (valid && node.getType() != null && node.getInitializers() != null) { AnnotatedArrayType arrayType = atypeFactory.getAnnotatedType(node); checkArrayInitialization(arrayType.getComponentType(), node.getInitializers()); } return super.visitNewArray(node, p); } /** * Do not override this method! * Previously, this method contained some logic, but the main modifier of types was missing. * It has been merged with the TypeValidator below. * This method doesn't need to do anything, as the type is already validated. */ @Override public final Void visitParameterizedType(ParameterizedTypeTree node, Void p) { return null; // super.visitParameterizedType(node, p); } protected void checkTypecastRedundancy(TypeCastTree node, Void p) { if (!checker.getLintOption("cast:redundant", false)) { return; } AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(node); AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(node.getExpression()); if (AnnotatedTypes.areSame(castType, exprType)) { checker.report(Result.warning("cast.redundant", castType), node); } } protected void checkTypecastSafety(TypeCastTree node, Void p) { if (!checker.getLintOption("cast:unsafe", true)) { return; } boolean isSubtype = false; // We cannot do a simple test of casting, as isSubtypeOf requires // the input types to be subtypes according to Java AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(node); if (castType.getKind() == TypeKind.DECLARED) { // eliminate false positives, where the annotations are // implicitly added by the declared type declaration AnnotatedDeclaredType castDeclared = (AnnotatedDeclaredType)castType; AnnotatedDeclaredType elementType = atypeFactory.fromElement((TypeElement)castDeclared.getUnderlyingType().asElement()); if (AnnotationUtils.areSame(castDeclared.getAnnotations(), elementType.getAnnotations())) { isSubtype = true; } } AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(node.getExpression()); if (!isSubtype) { if (checker.hasOption("checkCastElementType")) { AnnotatedTypeMirror newCastType; if (castType.getKind() == TypeKind.TYPEVAR) { newCastType = ((AnnotatedTypeVariable)castType).getUpperBound(); } else { newCastType = castType; } AnnotatedTypeMirror newExprType; if (exprType.getKind() == TypeKind.TYPEVAR) { newExprType = ((AnnotatedTypeVariable)exprType).getUpperBound(); } else { newExprType = exprType; } isSubtype = atypeFactory.getTypeHierarchy().isSubtype(newExprType, newCastType); if (isSubtype) { if (newCastType.getKind() == TypeKind.ARRAY && newExprType.getKind() != TypeKind.ARRAY) { // Always warn if the cast contains an array, but the expression // doesn't, as in "(Object[]) o" where o is of type Object isSubtype = false; } else if (newCastType.getKind() == TypeKind.DECLARED && newExprType.getKind() == TypeKind.DECLARED) { int castSize = ((AnnotatedDeclaredType) newCastType).getTypeArguments().size(); int exprSize = ((AnnotatedDeclaredType) newExprType).getTypeArguments().size(); if (castSize != exprSize) { // Always warn if the cast and expression contain a different number of // type arguments, e.g. to catch a cast from "Object" to "List<@NonNull Object>". // TODO: the same number of arguments actually doesn't guarantee anything. isSubtype = false; } } } } else { // Only check the main qualifiers, ignoring array components and // type arguments. isSubtype = atypeFactory.getQualifierHierarchy().isSubtype( exprType.getEffectiveAnnotations(), castType.getEffectiveAnnotations()); } } if (!isSubtype) { checker.report(Result.warning("cast.unsafe", exprType, castType), node); } } @Override public Void visitTypeCast(TypeCastTree node, Void p) { // validate "node" instead of "node.getType()" to prevent duplicate errors. boolean valid = validateTypeOf(node) && validateTypeOf(node.getExpression()); if (valid) { checkTypecastSafety(node, p); checkTypecastRedundancy(node, p); } return super.visitTypeCast(node, p); // return scan(node.getExpression(), p); } @Override public Void visitInstanceOf(InstanceOfTree node, Void p) { validateTypeOf(node.getType()); return super.visitInstanceOf(node, p); } @Override public Void visitArrayAccess(ArrayAccessTree node, Void p) { Pair preAssCtxt = visitorState.getAssignmentContext(); try { visitorState.setAssignmentContext(null); scan(node.getExpression(), p); scan(node.getIndex(), p); } finally { visitorState.setAssignmentContext(preAssCtxt); } return null; } /** * Checks the type of the exception parameter * Subclasses should override checkExceptionParameter(CatchTree node) * rather than this method to change the behavior of this check. */ @Override public Void visitCatch(CatchTree node, Void p) { checkExceptionParameter(node); return super.visitCatch(node, p); } /** * Checks the type of a thrown exception. Subclasses should override * checkThrownExpression(ThrowTree node) rather than this method to change * the behavior of this check. */ @Override public Void visitThrow(ThrowTree node, Void p) { checkThrownExpression(node); return super.visitThrow(node, p); } // ********************************************************************** // Helper methods to provide a single overriding point // ********************************************************************** /** * Issue error if the exception parameter is not a supertype of the * annotation specified by * {@link #getExceptionParameterLowerBoundAnnotations()}, * which is top by default. *

* * Subclasses may override this method to change the behavior of * this check. Subclasses wishing to enforce that exception parameter be * annotated with other annotations can just override * {@link #getExceptionParameterLowerBoundAnnotations()}. * * @param node * CatchTree to check */ protected void checkExceptionParameter(CatchTree node) { Set requiredAnnotations = getExceptionParameterLowerBoundAnnotations(); AnnotatedTypeMirror exPar = atypeFactory.getAnnotatedType(node .getParameter()); for (AnnotationMirror required : requiredAnnotations) { AnnotationMirror found = exPar.getAnnotationInHierarchy(required); assert found != null; if (!atypeFactory.getQualifierHierarchy() .isSubtype(required, found)) { checker.report(Result.failure("exception.parameter.invalid", found, required), node.getParameter()); } if (exPar.getKind() == TypeKind.UNION) { AnnotatedUnionType aut = (AnnotatedUnionType) exPar; for (AnnotatedTypeMirror alterntive : aut.getAlternatives()) { AnnotationMirror foundAltern = alterntive .getAnnotationInHierarchy(required); if (!atypeFactory.getQualifierHierarchy().isSubtype( required, foundAltern)) { checker.report(Result.failure( "exception.parameter.invalid", foundAltern, required), node.getParameter()); } } } } } /** * Returns a set of AnnotationMirrors that is a lower bound for exception * parameters. * * Note: by default this method is called by getThrowUpperBoundAnnotations(), so that * this annotation is enforced. * * (Default is top) * * @return set of annotation mirrors, one per hierarchy, that from a lower * bound of annotations that can be written on an exception * parameter */ protected Set getExceptionParameterLowerBoundAnnotations() { return atypeFactory.getQualifierHierarchy().getTopAnnotations(); } /** * Checks the type of the thrown expression. *

* * By default, this method checks that the thrown expression is a subtype of top. *

* * Issue error if the thrown expression is not a sub type of the * the annotation given by {@link #getThrowUpperBoundAnnotations()}, * the same as {@link #getExceptionParameterLowerBoundAnnotations()} * by default. *

* * Subclasses may override this method to change the behavior of this check. * Subclasses wishing to enforce that the thrown expression be a subtype of a type besides * {@link #getExceptionParameterLowerBoundAnnotations}, should override * {@link #getThrowUpperBoundAnnotations()}. * * @param node ThrowTree to check */ protected void checkThrownExpression(ThrowTree node) { AnnotatedTypeMirror throwType = atypeFactory.getAnnotatedType(node .getExpression()); Set required = getThrowUpperBoundAnnotations(); switch (throwType.getKind()) { case NULL: case DECLARED: Set found = throwType.getAnnotations(); if (!atypeFactory.getQualifierHierarchy() .isSubtype(found, required)) { checker.report( Result.failure("throw.type.invalid", found, required), node.getExpression()); } break; case TYPEVAR: case WILDCARD: //TODO: this code might change after the type var changes. Set foundEffective = throwType.getEffectiveAnnotations(); if (!atypeFactory.getQualifierHierarchy() .isSubtype(foundEffective, required)) { checker.report( Result.failure("throw.type.invalid", foundEffective, required), node.getExpression()); } break; case UNION: AnnotatedUnionType unionType = (AnnotatedUnionType) throwType; Set foundPrimary = unionType.getAnnotations(); if (!atypeFactory.getQualifierHierarchy().isSubtype(foundPrimary, required)) { checker.report(Result.failure("throw.type.invalid", foundPrimary, required), node.getExpression()); } for (AnnotatedTypeMirror altern : unionType.getAlternatives()) { if (!atypeFactory.getQualifierHierarchy().isSubtype( altern.getAnnotations(), required)) { checker.report( Result.failure("throw.type.invalid", altern.getAnnotations(), required), node.getExpression()); } } break; default: ErrorReporter.errorAbort("Unexpected throw expression type: " + throwType.getKind()); break; } } /** * Returns a set of AnnotationMirrors that is a upper bound for thrown * exceptions. * * Note: by default this method is returns by getExceptionParameterLowerBoundAnnotations(), so that * this annotation is enforced. * * (Default is top) * * @return set of annotation mirrors, one per hierarchy, that form an upper * bound of thrown expressions */ protected Set getThrowUpperBoundAnnotations() { return getExceptionParameterLowerBoundAnnotations(); } /** * Checks the validity of an assignment (or pseudo-assignment) from a value * to a variable and emits an error message (through the compiler's * messaging interface) if it is not valid. * * @param varTree the AST node for the variable * @param valueExp the AST node for the value * @param errorKey the error message to use if the check fails (must be a * compiler message key, see {@link org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey}) */ protected void commonAssignmentCheck(Tree varTree, ExpressionTree valueExp, /*@CompilerMessageKey*/ String errorKey) { AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(varTree); assert var != null : "no variable found for tree: " + varTree; if (!validateType(varTree, var)) { return; } checkAssignability(var, varTree); commonAssignmentCheck(var, valueExp, errorKey); } /** * Checks the validity of an assignment (or pseudo-assignment) from a value * to a variable and emits an error message (through the compiler's * messaging interface) if it is not valid. * * @param varType the annotated type of the variable * @param valueExp the AST node for the value * @param errorKey the error message to use if the check fails (must be a * compiler message key, see {@link org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey}) */ protected void commonAssignmentCheck(AnnotatedTypeMirror varType, ExpressionTree valueExp, /*@CompilerMessageKey*/ String errorKey) { if (shouldSkipUses(valueExp)) { return; } if (varType.getKind() == TypeKind.ARRAY && valueExp instanceof NewArrayTree && ((NewArrayTree) valueExp).getType() == null) { AnnotatedTypeMirror compType = ((AnnotatedArrayType) varType).getComponentType(); NewArrayTree arrayTree = (NewArrayTree) valueExp; assert arrayTree.getInitializers() != null : "array initializers are not expected to be null in: " + valueExp; checkArrayInitialization(compType, arrayTree.getInitializers()); } if (!validateTypeOf(valueExp)) { return; } AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueExp); assert valueType != null : "null type for expression: " + valueExp; commonAssignmentCheck(varType, valueType, valueExp, errorKey); } /** * Checks the validity of an assignment (or pseudo-assignment) from a value * to a variable and emits an error message (through the compiler's * messaging interface) if it is not valid. * * @param varType the annotated type of the variable * @param valueType the annotated type of the value * @param valueTree the location to use when reporting the error message * @param errorKey the error message to use if the check fails (must be a * compiler message key, see {@link org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey}) */ protected void commonAssignmentCheck(AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, /*@CompilerMessageKey*/ String errorKey) { String valueTypeString = valueType.toString(); String varTypeString = varType.toString(); // If both types as strings are the same, try outputting // the type including also invisible qualifiers. // This usually means there is a mistake in type defaulting. // This code is therefore not covered by a test. if (valueTypeString.equals(varTypeString)) { valueTypeString = valueType.toString(true); varTypeString = varType.toString(true); } if (checker.hasOption("showchecks")) { long valuePos = positions.getStartPosition(root, valueTree); System.out.printf( " %s (line %3d): %s %s%n actual: %s %s%n expected: %s %s%n", "About to test whether actual is a subtype of expected", (root.getLineMap() != null ? root.getLineMap().getLineNumber(valuePos) : -1), valueTree.getKind(), valueTree, valueType.getKind(), valueTypeString, varType.getKind(), varTypeString); } boolean success = atypeFactory.getTypeHierarchy().isSubtype(valueType, varType); // TODO: integrate with subtype test. if (success) { for (Class mono : atypeFactory.getSupportedMonotonicTypeQualifiers()) { if (valueType.hasAnnotation(mono) && varType.hasAnnotation(mono)) { checker.report( Result.failure("monotonic.type.incompatible", mono.getCanonicalName(), mono.getCanonicalName(), valueType.toString()), valueTree); return; } } } if (checker.hasOption("showchecks")) { long valuePos = positions.getStartPosition(root, valueTree); System.out.printf( " %s (line %3d): %s %s%n actual: %s %s%n expected: %s %s%n", (success ? "success: actual is subtype of expected" : "FAILURE: actual is not subtype of expected"), (root.getLineMap() != null ? root.getLineMap().getLineNumber(valuePos) : -1), valueTree.getKind(), valueTree, valueType.getKind(), valueTypeString, varType.getKind(), varTypeString); } // Use an error key only if it's overridden by a checker. if (!success) { checker.report(Result.failure(errorKey, valueTypeString, varTypeString), valueTree); } } protected void checkArrayInitialization(AnnotatedTypeMirror type, List initializers) { // TODO: set assignment context like for method arguments? // Also in AbstractFlow. for (ExpressionTree init : initializers) { commonAssignmentCheck(type, init, "array.initializer.type.incompatible"); } } /** * Checks that the annotations on the type arguments supplied to a type or a * method invocation are within the bounds of the type variables as * declared, and issues the "type.argument.type.incompatible" error if they are * not. * * @param toptree the tree for error reporting, only used for inferred type arguments * @param paramBounds the bounds of the type parameters from a class or method declaration * @param typeargs the type arguments from the type or method invocation * @param typeargTrees the type arguments as trees, used for error reporting */ // TODO: see updated version below that performs more well-formedness checks. protected void checkTypeArguments(Tree toptree, List paramBounds, List typeargs, List typeargTrees) { // System.out.printf("BaseTypeVisitor.checkTypeArguments: %s, TVs: %s, TAs: %s, TATs: %s\n", // toptree, paramBounds, typeargs, typeargTrees); // If there are no type variables, do nothing. if (paramBounds.isEmpty()) { return; } assert paramBounds.size() == typeargs.size() : "BaseTypeVisitor.checkTypeArguments: mismatch between type arguments: " + typeargs + " and type parameter bounds" + paramBounds; Iterator boundsIter = paramBounds.iterator(); Iterator argIter = typeargs.iterator(); while (boundsIter.hasNext()) { AnnotatedTypeParameterBounds bounds = boundsIter.next(); AnnotatedTypeMirror typeArg = argIter.next(); if (shouldBeCaptureConverted(typeArg, bounds)) { continue; } AnnotatedTypeMirror paramUpperBound = bounds.getUpperBound(); if (typeArg.getKind() == TypeKind.WILDCARD) { paramUpperBound = atypeFactory.widenToUpperBound(paramUpperBound, (AnnotatedWildcardType) typeArg); } if (typeargTrees == null || typeargTrees.isEmpty()) { // The type arguments were inferred and we mark the whole method. // The inference fails if we provide invalid arguments, // therefore issue an error for the arguments. // I hope this is less confusing for users. commonAssignmentCheck(paramUpperBound, typeArg, toptree, "type.argument.type.incompatible"); } else { commonAssignmentCheck(paramUpperBound, typeArg, typeargTrees.get(typeargs.indexOf(typeArg)), "type.argument.type.incompatible"); } if (!atypeFactory.getTypeHierarchy().isSubtype(bounds.getLowerBound(), typeArg)) { if (typeargTrees == null || typeargTrees.isEmpty()) { // The type arguments were inferred and we mark the whole method. checker.report(Result.failure("type.argument.type.incompatible", typeArg, bounds), toptree); } else { checker.report(Result.failure("type.argument.type.incompatible", typeArg, bounds), typeargTrees.get(typeargs.indexOf(typeArg))); } } } } //TODO: REMOVE WHEN CAPTURE CONVERSION IS IMPLEMENTED //TODO: This may not occur only in places where capture conversion occurs but in those cases //TODO: The containment check provided by this method should be enough /** * Identifies cases that would not happen if capture conversion were implemented. These special cases * should be removed when capture conversion is implemented. */ private boolean shouldBeCaptureConverted(final AnnotatedTypeMirror typeArg, final AnnotatedTypeParameterBounds bounds) { return typeArg.getKind() == TypeKind.WILDCARD && bounds.getUpperBound().getKind() == TypeKind.WILDCARD; } /* Updated version that performs more well-formedness checks. protected void checkTypeArguments(Tree toptree, List typevars, List typeargs, List typeargTrees) { // System.out.printf("BaseTypeVisitor.checkTypeArguments: %s, TVs: %s, TAs: %s, TATs: %s\n", // toptree, typevars, typeargs, typeargTrees); // If there are no type variables, do nothing. if (typevars.isEmpty()) { return; } assert typevars.size() == typeargs.size() : "BaseTypeVisitor.checkTypeArguments: mismatch between type arguments: " + typeargs + " and type variables " + typevars; assert typeargTrees.isEmpty() || typeargTrees.size() == typeargs.size() : "BaseTypeVisitor.checkTypeArguments: mismatch between type arguments: " + typeargs + " and their trees " + typeargTrees; Iterator varIter = typevars.iterator(); Iterator argIter = typeargs.iterator(); Iterator argTreeIter = typeargTrees.iterator(); while (varIter.hasNext()) { AnnotatedTypeVariable typeVar = varIter.next(); AnnotatedTypeMirror typearg = argIter.next(); Tree typeArgTree = null; if (argTreeIter.hasNext()) { typeArgTree = argTreeIter.next(); } if (typeArgTree != null) { boolean valid = validateType(typeArgTree, typearg); if (!valid) { // validateType already issued an error; check the next argument. continue; } } else { if (!AnnotatedTypes.isValidType(atypeFactory.getQualifierHierarchy(), typearg)) { continue; } typeArgTree = toptree; } if (typeVar.getUpperBound() != null) { if (!AnnotatedTypes.isValidType(atypeFactory.getQualifierHierarchy(), typeVar.getUpperBound())) { continue; } commonAssignmentCheck(typeVar.getUpperBound(), typearg, typeArgTree, "type.argument.type.incompatible", false); } // Should we compare lower bounds instead of the annotations on the // type variables? if (!typeVar.getAnnotations().isEmpty()) { if (!typearg.getEffectiveAnnotations().equals(typeVar.getEffectiveAnnotations())) { checker.report(Result.failure("type.argument.type.incompatible", typearg, typeVar), typeArgTree); } } } } */ /** * Indicates whether to skip subtype checks on the receiver when * checking method invocability. A visitor may, for example, * allow a method to be invoked even if the receivers are siblings * in a hierarchy, provided that some other condition (implemented * by the visitor) is satisfied. * * @param node the method invocation node * @param methodDefinitionReceiver the ATM of the receiver of the method definition * @param methodCallReceiver the ATM of the receiver of the method call * * @return whether to skip subtype checks on the receiver */ protected boolean skipReceiverSubtypeCheck(MethodInvocationTree node, AnnotatedTypeMirror methodDefinitionReceiver, AnnotatedTypeMirror methodCallReceiver) { return false; } /** * Tests whether the method can be invoked using the receiver of the 'node' * method invocation, and issues a "method.invocation.invalid" if the * invocation is invalid. * * This implementation tests whether the receiver in the method invocation * is a subtype of the method receiver type. This behavior can be specialized * by overriding skipReceiverSubtypeCheck. * * @param method the type of the invoked method * @param node the method invocation node */ protected void checkMethodInvocability(AnnotatedExecutableType method, MethodInvocationTree node) { if (method.getReceiverType() == null) { // Static methods don't have a receiver. return; } if (method.getElement().getKind() == ElementKind.CONSTRUCTOR) { // TODO: Explicit "this()" calls of constructors have an implicit passed // from the enclosing constructor. We must not use the self type, but // instead should find a way to determine the receiver of the enclosing constructor. // rcv = ((AnnotatedExecutableType)atypeFactory.getAnnotatedType(atypeFactory.getEnclosingMethod(node))).getReceiverType(); return; } AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(node); treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); if (!skipReceiverSubtypeCheck(node, methodReceiver, rcv) && !atypeFactory.getTypeHierarchy().isSubtype(treeReceiver, methodReceiver)) { checker.report(Result.failure("method.invocation.invalid", TreeUtils.elementFromUse(node), treeReceiver.toString(), methodReceiver.toString()), node); } } protected boolean checkConstructorInvocation(AnnotatedDeclaredType invocation, AnnotatedExecutableType constructor, NewClassTree newClassTree) { AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) constructor.getReturnType(); // When an interface is used as the identifier in an anonymous class (e.g. new Comparable() {}) // the constructor method will be Object.init() {} which has an Object return type // When TypeHierarchy attempts to convert it to the supertype (e.g. Comparable) it will return // null from asSuper and return false for the check. Instead, copy the primary annotations // to the declared type and then do a subtyping check if (invocation.getUnderlyingType().asElement().getKind().isInterface() && TypesUtils.isObject(returnType.getUnderlyingType())) { final AnnotatedDeclaredType retAsDt = invocation.deepCopy(); retAsDt.replaceAnnotations(returnType.getAnnotations()); returnType = retAsDt; } else if (newClassTree.getClassBody() != null) { // An anonymous class invokes the constructor of it's super class, so the underlying // types of invocation and returnType are not the same. Call asSuper so they are the // same and the is subtype tests below work correctly invocation = (AnnotatedDeclaredType) AnnotatedTypes.asSuper(types, atypeFactory, invocation, returnType); } // The return type of the constructor (returnType) must be comparable to the type of the // constructor invocation (invocation). if (!(atypeFactory.getTypeHierarchy().isSubtype(invocation, returnType) || atypeFactory.getTypeHierarchy().isSubtype(returnType, invocation))) { checker.report(Result.failure("constructor.invocation.invalid", constructor.toString(), invocation, returnType), newClassTree); return false; } return true; // TODO: what properties should hold for constructor receivers for // inner type instantiations? } /** * A helper method to check that each passed argument is a subtype of the * corresponding required argument, and issues "argument.invalid" error * for each passed argument that not a subtype of the required one. * * Note this method requires the lists to have the same length, as it * does not handle cases like var args. * * @param requiredArgs the required types * @param passedArgs the expressions passed to the corresponding types */ protected void checkArguments(List requiredArgs, List passedArgs) { assert requiredArgs.size() == passedArgs.size() : "mismatch between required args (" + requiredArgs + ") and passed args (" + passedArgs + ")"; Pair preAssCtxt = visitorState.getAssignmentContext(); try { for (int i = 0; i < requiredArgs.size(); ++i) { visitorState.setAssignmentContext(Pair.of((Tree) null, (AnnotatedTypeMirror) requiredArgs.get(i))); commonAssignmentCheck(requiredArgs.get(i), passedArgs.get(i), "argument.type.incompatible"); // Also descend into the argument within the correct assignment // context. scan(passedArgs.get(i), null); } } finally { visitorState.setAssignmentContext(preAssCtxt); } } /** * @return true if both types are type variables and outer contains inner * Outer contains inner implies: * {@literal * inner.upperBound <: outer.upperBound * outer.lowerBound <: inner.lowerBound * } */ protected boolean testTypevarContainment(final AnnotatedTypeMirror inner, final AnnotatedTypeMirror outer) { if (inner.getKind() == TypeKind.TYPEVAR && outer.getKind() == TypeKind.TYPEVAR) { final AnnotatedTypeVariable innerAtv = (AnnotatedTypeVariable) inner; final AnnotatedTypeVariable outerAtv = (AnnotatedTypeVariable) outer; if (AnnotatedTypes.areCorrespondingTypeVariables(elements, innerAtv, outerAtv)) { final TypeHierarchy typeHierarchy = atypeFactory.getTypeHierarchy(); return typeHierarchy.isSubtype(innerAtv.getUpperBound(), outerAtv.getUpperBound()) && typeHierarchy.isSubtype(outerAtv.getLowerBound(), innerAtv.getLowerBound()); } } return false; } /** * Type checks that a method may override another method. * Uses the OverrideChecker class. * * @param overriderTree declaration tree of overriding method * @param overridingType type of overriding class * @param overridden type of overridden method * @param overriddenType type of overridden class * @return true if the override is allowed */ protected boolean checkOverride(MethodTree overriderTree, AnnotatedDeclaredType overridingType, AnnotatedExecutableType overridden, AnnotatedDeclaredType overriddenType, Void p) { // Get the type of the overriding method. AnnotatedExecutableType overrider = atypeFactory.getAnnotatedType(overriderTree); // This needs to be done before overrider.getReturnType() and overridden.getReturnType() if (overrider.getTypeVariables().isEmpty() && !overridden.getTypeVariables().isEmpty()) { overridden = overridden.getErased(); } OverrideChecker overrideChecker = new OverrideChecker( overriderTree, overrider, overridingType, overrider.getReturnType(), overridden, overriddenType, overridden.getReturnType()); return overrideChecker.checkOverride(); } // Only issue the methodref.inference.unimplemented message once private static boolean typeArgumentInferenceCheck = false; /** * Check that a method reference is allowed. * Using the OverrideChecker class. * * @param memberReferenceTree the tree for the method reference * @return true if the method reference is allowed */ protected boolean checkMethodReferenceAsOverride(MemberReferenceTree memberReferenceTree, Void p) { Pair result = atypeFactory.getFnInterfaceFromTree(memberReferenceTree); AnnotatedDeclaredType overriddenType = result.first; AnnotatedExecutableType overriddenMethodType = result.second; // ========= Overriding Type ========= // Get declared type from ::method or ::method // This doesn't get the correct type for a "MyOuter.super" based on the receiver of the enclosing method. // That is handled separately in method receiver check. // TODO: Class type argument inference AnnotatedTypeMirror overridingType = atypeFactory.getAnnotatedType(memberReferenceTree.getQualifierExpression()); // ========= Overriding Executable ========= // The ::method element ExecutableElement overridingElement = (ExecutableElement)InternalUtils.symbol(memberReferenceTree); AnnotatedExecutableType overridingMethodType = atypeFactory.methodFromUse(memberReferenceTree, overridingElement, overridingType).first; if (checkMethodReferenceInference(memberReferenceTree, overridingMethodType, overriddenMethodType, overridingType)) { // Type argument inference is required, skip check. // #checkMethodReferenceInference issued a warning. return true; } // This needs to be done before overridingMethodType.getReturnType() and overriddenMethodType.getReturnType() if (overridingMethodType.getTypeVariables().isEmpty() && !overriddenMethodType.getTypeVariables().isEmpty()) { overriddenMethodType = overriddenMethodType.getErased(); } // Use the functional interface's parameters to resolve poly quals. QualifierPolymorphism poly = new QualifierPolymorphism(atypeFactory.getProcessingEnv(), atypeFactory); poly.annotate(overriddenMethodType, overridingMethodType); AnnotatedTypeMirror overridingReturnType; if (overridingElement.getKind() == ElementKind.CONSTRUCTOR) { if (overridingType.getKind() == TypeKind.ARRAY) { // Special casing for the return of array constructor overridingReturnType = overridingType; } else { overridingReturnType = atypeFactory.getResultingTypeOfConstructorMemberReference(memberReferenceTree, overridingMethodType); } } else { overridingReturnType = overridingMethodType.getReturnType(); } AnnotatedTypeMirror overriddenReturnType = overriddenMethodType.getReturnType(); if (overriddenReturnType.getKind() == TypeKind.VOID) { // If the functional interface return type is void, the overriding return // type doesn't matter. overriddenReturnType = overridingReturnType; } OverrideChecker overrideChecker = new OverrideChecker( memberReferenceTree, overridingMethodType, overridingType, overridingReturnType, overriddenMethodType, overriddenType, overriddenReturnType); return overrideChecker.checkOverride(); } /** * Check if method reference type argument inference is required. Issue an error if * is is. */ private boolean checkMethodReferenceInference(MemberReferenceTree memberReferenceTree, AnnotatedExecutableType memberReferenceType, AnnotatedExecutableType overridden, AnnotatedTypeMirror overridingType) { // TODO: Method type argument inference // TODO: Enable checks for method reference with inferred type arguments. // For now, error on mismatch of class or method type arguments. if (overridden.getTypeVariables().size() == 0) { boolean requiresInference = false; // The functional interface does not have any method type parameters if (memberReferenceType.getTypeVariables().size() > 0 && (memberReferenceTree.getTypeArguments() == null || memberReferenceTree.getTypeArguments().size() == 0)) { // Method type args requiresInference = true; } else if (overridingType.getKind() == TypeKind.DECLARED && ((AnnotatedDeclaredType)overridingType).getTypeArguments().size() > 0) { // Class type args if (memberReferenceTree.getQualifierExpression().getKind() != Tree.Kind.PARAMETERIZED_TYPE) { requiresInference = true; } else if (((AnnotatedDeclaredType)overridingType).getTypeArguments().size() != ((ParameterizedTypeTree) memberReferenceTree.getQualifierExpression()).getTypeArguments().size()) { requiresInference = true; } } if (requiresInference) { if (!typeArgumentInferenceCheck) { checker.report(Result.warning("methodref.inference.unimplemented"), memberReferenceTree); typeArgumentInferenceCheck = true; } return true; } } return false; } /** * Class to perform method override and method reference checks. * * Method references are checked similarly to method overrides, with the * method reference viewed as overriding the functional interface's method. * * Checks that an overriding method's return type, parameter types, and * receiver type are correct with respect to the annotations on the * overridden method's return type, parameter types, and receiver type. * *

* Furthermore, any contracts on the method must satisfy behavioral * subtyping, that is, postconditions must be at least as strong as the * postcondition on the superclass, and preconditions must be at most as * strong as the condition on the superclass. * *

* This method returns the result of the check, but also emits error * messages as a side effect. */ private class OverrideChecker { // Strings for printing private final String overriderMeth; private final String overriderTyp; private final String overriddenMeth; private final String overriddenTyp; private final Tree overriderTree; private final Boolean methodReference; private final AnnotatedExecutableType overrider; private final AnnotatedTypeMirror overridingType; private final AnnotatedExecutableType overridden; private final AnnotatedDeclaredType overriddenType; private final AnnotatedTypeMirror overriddenReturnType; private final AnnotatedTypeMirror overridingReturnType; /** * Create an OverrideChecker. * * Notice that the return types are passed in separately. This is to * support some types of method references where the overrider's return * type is not the appropriate type to check. * * @param overriderTree * the AST node of the overriding method or method reference * @param overrider * the type of the overriding method * @param overridingType * the type enclosing the overrider method, usually an AnnotatedDeclaredType; * for Method References may be something else. * @param overridingReturnType * the return type of the overriding method * @param overridden * the type of the overridden method * @param overriddenType * the declared type enclosing the overridden method * @param overriddenReturnType * the return type of the overridden method */ OverrideChecker(Tree overriderTree, AnnotatedExecutableType overrider, AnnotatedTypeMirror overridingType, AnnotatedTypeMirror overridingReturnType, AnnotatedExecutableType overridden, AnnotatedDeclaredType overriddenType, AnnotatedTypeMirror overriddenReturnType) { this.overriderTree = overriderTree; this.overrider = overrider; this.overridingType = overridingType; this.overridden = overridden; this.overriddenType = overriddenType; this.overriddenReturnType = overriddenReturnType; this.overridingReturnType = overridingReturnType; overriderMeth = overrider.toString(); if (overridingType.getKind() == TypeKind.DECLARED) { DeclaredType overriderTypeMirror = ((AnnotatedDeclaredType)overridingType).getUnderlyingType(); overriderTyp = overriderTypeMirror.asElement().toString(); } else { overriderTyp = overridingType.toString(); } overriddenMeth = overridden.toString(); overriddenTyp = overriddenType.getUnderlyingType().asElement().toString(); this.methodReference = overriderTree.getKind() == Tree.Kind.MEMBER_REFERENCE; } /** * Perform the check * * @return true if the override is allowed */ public boolean checkOverride() { if (checker.shouldSkipUses(overriddenType.getUnderlyingType().asElement())) { return true; } boolean result = checkReturn(); result &= checkParameters(); if (methodReference) { result &= checkMemberReferenceReceivers(); } else { result &= checkReceiverOverride(); } checkPreAndPostConditions(); checkPurity(); return result; } private void checkPurity() { String msgKey = methodReference ? "purity.invalid.methodref" : "purity.invalid.overriding"; // check purity annotations Set superPurity = new HashSet( PurityUtils.getPurityKinds(atypeFactory, overridden.getElement())); Set subPurity = new HashSet( PurityUtils.getPurityKinds(atypeFactory, overrider.getElement())); if (!subPurity.containsAll(superPurity)) { checker.report(Result.failure(msgKey, overriderMeth, overriderTyp, overriddenMeth, overriddenTyp, subPurity, superPurity), overriderTree); } } private void checkPreAndPostConditions() { String msgKey = methodReference ? "methodref" : "override"; if (methodReference) { // TODO: Support post conditions and method references. // The parse context always expects instance methods, but method references can be static. return; } // Check postconditions ContractsUtils contracts = ContractsUtils.getInstance(atypeFactory); Set> superPost = contracts .getPostconditions(overridden.getElement()); Set> subPost = contracts .getPostconditions(overrider.getElement()); Set> superPost2 = resolveContracts(superPost, overridden); Set> subPost2 = resolveContracts(subPost, overrider); @SuppressWarnings("CompilerMessages") /*@CompilerMessageKey*/ String postmsg = "contracts.postcondition." + msgKey + ".invalid"; checkContractsSubset(overriderMeth, overriderTyp, overriddenMeth, overriddenTyp, superPost2, subPost2, postmsg); // Check preconditions Set> superPre = contracts .getPreconditions(overridden.getElement()); Set> subPre = contracts.getPreconditions(overrider .getElement()); Set> superPre2 = resolveContracts(superPre, overridden); Set> subPre2 = resolveContracts(subPre, overrider); @SuppressWarnings("CompilerMessages") /*@CompilerMessageKey*/ String premsg = "contracts.precondition." + msgKey + ".invalid"; checkContractsSubset(overriderMeth, overriderTyp, overriddenMeth, overriddenTyp, subPre2, superPre2, premsg); // Check conditional postconditions Set>> superCPost = contracts .getConditionalPostconditions(overridden.getElement()); Set>> subCPost = contracts .getConditionalPostconditions(overrider.getElement()); // consider only 'true' postconditions Set> superCPostTrue = filterConditionalPostconditions( superCPost, true); Set> subCPostTrue = filterConditionalPostconditions( subCPost, true); Set> superCPostTrue2 = resolveContracts( superCPostTrue, overridden); Set> subCPostTrue2 = resolveContracts( subCPostTrue, overrider); @SuppressWarnings("CompilerMessages") /*@CompilerMessageKey*/ String posttruemsg = "contracts.conditional.postcondition.true." + msgKey + ".invalid"; checkContractsSubset(overriderMeth, overriderTyp, overriddenMeth, overriddenTyp, superCPostTrue2, subCPostTrue2, posttruemsg); Set> superCPostFalse = filterConditionalPostconditions( superCPost, false); Set> subCPostFalse = filterConditionalPostconditions( subCPost, false); Set> superCPostFalse2 = resolveContracts( superCPostFalse, overridden); Set> subCPostFalse2 = resolveContracts( subCPostFalse, overrider); @SuppressWarnings("CompilerMessages") /*@CompilerMessageKey*/ String postfalsemsg = "contracts.conditional.postcondition.false." + msgKey + ".invalid"; checkContractsSubset(overriderMeth, overriderTyp, overriddenMeth, overriddenTyp, superCPostFalse2, subCPostFalse2, postfalsemsg); } private boolean checkMemberReferenceReceivers() { JCTree.JCMemberReference memberTree = (JCTree.JCMemberReference) overriderTree; if (overridingType.getKind() == TypeKind.ARRAY) { // Assume the receiver for all method on arrays are @Top // This simplifies some logic because an AnnotatedExecutableType for an array method // (ie String[]::clone) has a receiver of "Array." The UNBOUND check would then // have to compare "Array" to "String[]". return true; } // These act like a traditional override if (memberTree.kind == JCTree.JCMemberReference.ReferenceKind.UNBOUND) { AnnotatedTypeMirror overriderReceiver = overrider.getReceiverType(); AnnotatedTypeMirror overriddenReceiver = overridden.getParameterTypes().get(0); boolean success = atypeFactory.getTypeHierarchy().isSubtype(overriddenReceiver, overriderReceiver); if (!success) { checker.report(Result.failure("methodref.receiver.invalid", overriderMeth, overriderTyp, overriddenMeth, overriddenTyp, overriderReceiver, overriddenReceiver), overriderTree); } return success; } // The rest act like method invocations AnnotatedTypeMirror receiverDecl; AnnotatedTypeMirror receiverArg; switch (memberTree.kind) { case UNBOUND: ErrorReporter.errorAbort("Case UNBOUND should already be handled."); return true; // Dead code case SUPER: receiverDecl = overrider.getReceiverType(); receiverArg = atypeFactory.getAnnotatedType(memberTree.getQualifierExpression()); final AnnotatedTypeMirror selfType = atypeFactory.getSelfType(memberTree); receiverArg.replaceAnnotations(selfType.getAnnotations()); break; case BOUND: receiverDecl = overrider.getReceiverType(); receiverArg = overridingType; break; case IMPLICIT_INNER: receiverDecl = overrider.getReceiverType(); receiverArg = atypeFactory.getSelfType(memberTree); break; case TOPLEVEL: case STATIC: case ARRAY_CTOR: default: // Intentional fallthrough // These don't have receivers return true; } boolean success = atypeFactory.getTypeHierarchy().isSubtype(receiverArg, receiverDecl); if (!success) { checker.report(Result.failure("methodref.receiver.bound.invalid", receiverArg, overriderMeth, overriderTyp, receiverArg, receiverDecl), overriderTree); } return success; } private boolean checkReceiverOverride() { // Check the receiver type. // isSubtype() requires its arguments to be actual subtypes with // respect to JLS, but overrider receiver is not a subtype of the // overridden receiver. Hence copying the annotations. // TODO: this will need to be improved for generic receivers. AnnotatedTypeMirror overriddenReceiver = overrider.getReceiverType().getErased().shallowCopy(false); overriddenReceiver.addAnnotations(overridden.getReceiverType().getAnnotations()); if (!atypeFactory.getTypeHierarchy().isSubtype(overriddenReceiver, overrider.getReceiverType().getErased())) { checker.report(Result.failure("override.receiver.invalid", overriderMeth, overriderTyp, overriddenMeth, overriddenTyp, overrider.getReceiverType(), overridden.getReceiverType()), overriderTree); return false; } return true; } private boolean checkParameters() { boolean result = true; // Check parameter values. (TODO: FIXME varargs) List overriderParams = overrider.getParameterTypes(); List overriddenParams = overridden.getParameterTypes(); // The functional interface of an unbound member reference has an extra parameter (the receiver). if (methodReference && ((JCTree.JCMemberReference)overriderTree).hasKind(JCTree.JCMemberReference.ReferenceKind.UNBOUND)) { overriddenParams = new ArrayList<>(overriddenParams); overriddenParams.remove(0); } for (int i = 0; i < overriderParams.size(); ++i) { boolean success = atypeFactory.getTypeHierarchy().isSubtype(overriddenParams.get(i), overriderParams.get(i)); if (!success) { success = testTypevarContainment(overriddenParams.get(i), overriderParams.get(i)); } checkParametersMsg(success, i, overriderParams, overriddenParams); result &= success; } return result; } private void checkParametersMsg(boolean success, int index, List overriderParams, List overriddenParams) { String msgKey = methodReference ? "methodref.param.invalid" : "override.param.invalid"; long valuePos = overriderTree instanceof MethodTree ? positions.getStartPosition(root, ((MethodTree)overriderTree).getParameters().get(index)) : positions.getStartPosition(root, overriderTree); Tree posTree = overriderTree instanceof MethodTree ? ((MethodTree)overriderTree).getParameters().get(index) : overriderTree; if (checker.hasOption("showchecks")) { System.out.printf( " %s (line %3d):%n overrider: %s %s (parameter %d type %s)%n overridden: %s %s (parameter %d type %s)%n", (success ? "success: overridden parameter type is subtype of overriding" : "FAILURE: overridden parameter type is not subtype of overriding"), (root.getLineMap() != null ? root.getLineMap().getLineNumber(valuePos) : -1), overriderMeth, overriderTyp, index, overriderParams.get(index).toString(), overriddenMeth, overriddenTyp, index, overriddenParams.get(index).toString()); } if (!success) { checker.report(Result.failure(msgKey, overriderMeth, overriderTyp, overriddenMeth, overriddenTyp, overriderParams.get(index).toString(), overriddenParams.get(index).toString()), posTree); } } private boolean checkReturn() { boolean success = true; // Check the return value. if ((overridingReturnType.getKind() != TypeKind.VOID)) { final TypeHierarchy typeHierarchy = atypeFactory.getTypeHierarchy(); success = typeHierarchy.isSubtype(overridingReturnType, overriddenReturnType); // If both the overridden method have type variables as return types and both types were // defined in their respective methods then, they can be covariant or invariant // use super/subtypes for the overrides locations if (!success) { success = testTypevarContainment(overridingReturnType, overriddenReturnType); // sometimes when using a Java 8 compiler (not JSR308) the overridden return type of a method reference // becomes a captured type. This leads to defaulting that often makes the overriding return type // invalid. We ignore these. This happens in Issue403/Issue404 when running without JSR308 Langtools if (!success && methodReference) { boolean isCaptureConverted = (overriddenReturnType.getKind() == TypeKind.TYPEVAR) && InternalUtils.isCaptured((TypeVariable) overriddenReturnType.getUnderlyingType()); if (methodReference && isCaptureConverted) { ExecutableElement overridenMethod = overridden.getElement(); boolean isFunctionApply = overridenMethod.getSimpleName().toString().equals("apply") && overridenMethod.getEnclosingElement().toString().equals("java.util.function.Function"); if (isFunctionApply) { AnnotatedTypeMirror overridingUpperBound = ((AnnotatedTypeVariable) overriddenReturnType).getUpperBound(); success = typeHierarchy.isSubtype(overridingReturnType, overridingUpperBound); } } } } checkReturnMsg(success); } return success; } private void checkReturnMsg(boolean success) { String msgKey = methodReference ? "methodref.return.invalid" : "override.return.invalid"; long valuePos = overriderTree instanceof MethodTree ? positions.getStartPosition(root, ((MethodTree)overriderTree).getReturnType()) : positions.getStartPosition(root, overriderTree); Tree posTree = overriderTree instanceof MethodTree ? ((MethodTree)overriderTree).getReturnType() : overriderTree; // The return type of a MethodTree is null for a constructor. if (posTree == null) { posTree = overriderTree; } if (checker.hasOption("showchecks")) { System.out.printf( " %s (line %3d):%n overrider: %s %s (return type %s)%n overridden: %s %s (return type %s)%n", (success ? "success: overriding return type is subtype of overridden" : "FAILURE: overriding return type is not subtype of overridden"), (root.getLineMap() != null ? root.getLineMap().getLineNumber(valuePos) : -1), overriderMeth, overriderTyp, overrider.getReturnType().toString(), overriddenMeth, overriddenTyp, overridden.getReturnType().toString()); } if (!success) { checker.report(Result.failure(msgKey, overriderMeth, overriderTyp, overriddenMeth, overriddenTyp, overridingReturnType, overriddenReturnType), posTree); } } } /** * Filters the set of conditional postconditions to return only those with * {@code result=true}. */ private Set> filterConditionalPostconditions( Set>> conditionalPostconditions, boolean b) { Set> result = new HashSet<>(); for (Pair> p : conditionalPostconditions) { if (p.second.first == b) { result.add(Pair.of(p.first, p.second.second)); } } return result; } /** * Checks that {@code mustSubset} is a subset of {@code set} in the * following sense: For every expression in {@code mustSubset} there must be the * same expression in {@code set}, with the same (or a stronger) annotation. */ private void checkContractsSubset( String overriderMeth, String overriderTyp, String overriddenMeth, String overriddenTyp, Set> mustSubset, Set> set, /*@CompilerMessageKey*/ String messageKey) { for (Pair a : mustSubset) { boolean found = false; for (Pair b : set) { // are we looking at a contract of the same receiver? if (a.first.equals(b.first)) { // check subtyping relationship of annotations QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy(); if (qualifierHierarchy.isSubtype(a.second, b.second)) { found = true; break; } } } if (!found) { MethodTree method = visitorState.getMethodTree(); checker.report(Result.failure(messageKey, overriderMeth, overriderTyp, overriddenMeth, overriddenTyp, a.second, a.first), method); } } } /** * Takes a set of contracts identified by their expression and annotation * strings and resolves them to the correct {@link Receiver} and * {@link AnnotationMirror}. */ private Set> resolveContracts( Set> contractSet, AnnotatedExecutableType method) { Set> result = new HashSet<>(); MethodTree methodTree = visitorState.getMethodTree(); TreePath path = atypeFactory.getPath(methodTree); FlowExpressionContext flowExprContext = null; for (Pair p : contractSet) { String expression = p.first; AnnotationMirror annotation = AnnotationUtils.fromName( atypeFactory.getElementUtils(), p.second); // Only check if the postcondition concerns this checker if (!atypeFactory.isSupportedQualifier(annotation)) { continue; } if (flowExprContext == null) { flowExprContext = FlowExpressionParseUtil .buildFlowExprContextForDeclaration(methodTree, method .getReceiverType().getUnderlyingType(), checker.getContext()); } try { // TODO: currently, these expressions are parsed many times. // this could // be optimized to store the result the first time. // (same for other annotations) FlowExpressions.Receiver expr = FlowExpressionParseUtil.parse( expression, flowExprContext, path); result.add(Pair.of(expr, annotation)); } catch (FlowExpressionParseException e) { // errors are reported elsewhere + ignore this contract } } return result; } /** * Tests, for a re-assignment, whether the variable is assignable or not. If * not, it emits an assignability.invalid error. * * @param varType the type of the variable being re-assigned * @param varTree the tree used to access the variable in the assignment */ protected void checkAssignability(AnnotatedTypeMirror varType, Tree varTree) { if (TreeUtils.isExpressionTree(varTree)) { AnnotatedTypeMirror rcvType = atypeFactory.getReceiverType((ExpressionTree) varTree); if (!isAssignable(varType, rcvType, varTree)) { checker.report(Result.failure("assignability.invalid", InternalUtils.symbol(varTree), rcvType), varTree); } } } /** * Tests whether the variable accessed is an assignable variable or not, * given the current scope * * TODO: document which parameters are nullable; e.g. receiverType is null in * many cases, e.g. local variables. * * @param varType the annotated variable type * @param variable tree used to access the variable * @return true iff variable is assignable in the current scope */ protected boolean isAssignable(AnnotatedTypeMirror varType, AnnotatedTypeMirror receiverType, Tree variable) { return true; } protected MemberSelectTree enclosingMemberSelect() { TreePath path = this.getCurrentPath(); assert path.getLeaf().getKind() == Tree.Kind.IDENTIFIER : "expected identifier, found: " + path.getLeaf(); if (path.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) { return (MemberSelectTree) path.getParentPath().getLeaf(); } else { return null; } } protected Tree enclosingStatement(Tree tree) { TreePath path = this.getCurrentPath(); while (path != null && path.getLeaf() != tree) path = path.getParentPath(); if (path != null) { return path.getParentPath().getLeaf(); } else { return null; } } @Override public Void visitIdentifier(IdentifierTree node, Void p) { checkAccess(node, p); return super.visitIdentifier(node, p); } protected void checkAccess(IdentifierTree node, Void p) { MemberSelectTree memberSel = enclosingMemberSelect(); ExpressionTree tree; Element elem; if (memberSel == null) { tree = node; elem = TreeUtils.elementFromUse(node); } else { tree = memberSel; elem = TreeUtils.elementFromUse(memberSel); } if (elem == null || !elem.getKind().isField()) { return; } AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(tree); if (!isAccessAllowed(elem, receiver, tree)) { checker.report(Result.failure("unallowed.access", elem, receiver), node); } } protected boolean isAccessAllowed(Element field, AnnotatedTypeMirror receiver, ExpressionTree accessTree) { AnnotationMirror unused = atypeFactory.getDeclAnnotation(field, Unused.class); if (unused == null) { return true; } Name when = AnnotationUtils.getElementValueClassName(unused, "when", false); if (receiver.getAnnotation(when) == null) { return true; } Tree tree = this.enclosingStatement(accessTree); // assigning unused to null is OK return (tree != null && tree.getKind() == Tree.Kind.ASSIGNMENT && ((AssignmentTree)tree).getVariable() == accessTree && ((AssignmentTree)tree).getExpression().getKind() == Tree.Kind.NULL_LITERAL); } /** * Tests that the qualifiers present on the useType are valid qualifiers, * given the qualifiers on the declaration of the type, declarationType. * *

* * The check is shallow, as it does not descend into generic or array * types (i.e. only performing the validity check on the raw type or * outermost array dimension). {@link BaseTypeVisitor#validateTypeOf(Tree)} * would call this for each type argument or array dimension separately. * *

* * In most cases, {@code useType} simply needs to be a subtype of * {@code declarationType}, but there are exceptions. * * @param declarationType the type of the class (TypeElement) * @param useType the use of the class (instance type) * @param tree the tree where the type is used * @return true if the useType is a valid use of elemType */ public boolean isValidUse(AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) { return atypeFactory.getTypeHierarchy().isSubtype(useType.getErased(), declarationType.getErased()); } /** * Tests that the qualifiers present on the primitive type are valid. * * The default implementation always returns true. * Subclasses should override this method to limit what annotations are * allowed on primitive types. * * @param type the use of the primitive type * @param tree the tree where the type is used * @return true if the type is a valid use of the primitive type */ public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) { return true; } /** * Tests that the qualifiers present on the array type are valid. * This method will be invoked for each array level independently, i.e. this * method only needs to check the top-level qualifiers of an array. * * The default implementation always returns true. * Subclasses should override this method to limit what annotations are * allowed on array types. * * @param type the array type use * @param tree the tree where the type is used * @return true if the type is a valid array type */ public boolean isValidUse(AnnotatedArrayType type, Tree tree) { return true; } /** * Tests whether the tree expressed by the passed type tree is a valid type, * and emits an error if that is not the case (e.g. '@Mutable String'). * If the tree is a method or constructor, check the return type. * * @param tree the AST type supplied by the user */ public boolean validateTypeOf(Tree tree) { AnnotatedTypeMirror type; // It's quite annoying that there is no TypeTree switch (tree.getKind()) { case PRIMITIVE_TYPE: case PARAMETERIZED_TYPE: case TYPE_PARAMETER: case ARRAY_TYPE: case UNBOUNDED_WILDCARD: case EXTENDS_WILDCARD: case SUPER_WILDCARD: case ANNOTATED_TYPE: type = atypeFactory.getAnnotatedTypeFromTypeTree(tree); break; case METHOD: type = atypeFactory.getMethodReturnType((MethodTree) tree); if (type == null || type.getKind() == TypeKind.VOID) { // Nothing to do for void methods. // Note that for a constructor the AnnotatedExecutableType does // not use void as return type. return true; } break; default: type = atypeFactory.getAnnotatedType(tree); } return validateType(tree, type); } /** * Tests whether the type and corresponding type tree is a valid type, * and emits an error if that is not the case (e.g. '@Mutable String'). * If the tree is a method or constructor, check the return type. * * @param tree the type tree supplied by the user * @param type the type corresponding to tree */ public boolean validateType(Tree tree, AnnotatedTypeMirror type) { // basic consistency checks if (!AnnotatedTypes.isValidType(atypeFactory.getQualifierHierarchy(), type)) { checker.report(Result.failure("type.invalid", type.getAnnotations(), type.toString()), tree); return false; } // more checks (also specific to checker, potentially) return typeValidator.isValid(type, tree); } // This is a test to ensure that all types are valid protected final TypeValidator typeValidator; protected TypeValidator createTypeValidator() { return new BaseTypeValidator(checker, this, atypeFactory); } // ********************************************************************** // Random helper methods // ********************************************************************** /** * Tests whether the expression should not be checked because of the tree * referring to unannotated classes, as specified in * the {@code checker.skipUses} property. * * It returns true if exprTree is a method invocation or a field access * to a class whose qualified name matches @{link checker.skipUses} * expression. * * @param exprTree any expression tree * @return true if checker should not test exprTree */ protected final boolean shouldSkipUses(ExpressionTree exprTree) { // System.out.printf("shouldSkipUses: %s: %s%n", exprTree.getClass(), exprTree); // This special case for ConditionalExpressionTree seems wrong, so // I commented it out. It will skip expressions that should be // checked, just because they are lexically near expressions that // should be skipped. Presumably it's because conditionals do some // type inference, but if so, this is the wrong way to fix the // problem. -MDE // if (exprTree instanceof ConditionalExpressionTree) { // ConditionalExpressionTree condTree = // (ConditionalExpressionTree)exprTree; // return (shouldSkipUses(condTree.getTrueExpression()) || // shouldSkipUses(condTree.getFalseExpression())); // } // Don't use commonAssignmentCheck for lambdas or method references. if (exprTree instanceof MemberReferenceTree || exprTree instanceof LambdaExpressionTree) { return true; } Element elm = InternalUtils.symbol(exprTree); return checker.shouldSkipUses(elm); } // ********************************************************************** // Overriding to avoid visit part of the tree // ********************************************************************** /** * Override Compilation Unit so we won't visit package names or imports */ @Override public Void visitCompilationUnit(CompilationUnitTree node, Void p) { Void r = scan(node.getPackageAnnotations(), p); // r = reduce(scan(node.getPackageName(), p), r); // r = reduce(scan(node.getImports(), p), r); r = reduce(scan(node.getTypeDecls(), p), r); return r; } // ********************************************************************** // Check that the annotated JDK is being used. // ********************************************************************** private static boolean checkedJDK = false; // Not all subclasses call this -- only those that have an annotated JDK. /** Warn if the annotated JDK is not being used. */ protected void checkForAnnotatedJdk() { if (checkedJDK) { return; } checkedJDK = true; if (checker.hasOption("nocheckjdk")) { return; } TypeElement objectTE = elements.getTypeElement("java.lang.Object"); List members = elements.getAllMembers(objectTE); for (Element member : members) { if (member.toString().equals("equals(java.lang.Object)")) { ExecutableElement m = (ExecutableElement) member; // The Nullness JDK serves as a proxy for all annotated // JDKs. // Note that we cannot use the AnnotatedTypeMirrors from the // Checker Framework, because those only return the annotations // that are used by the current checker. // That is, if this code is executed by something other than the // Nullness Checker, we would not find the annotations. // Therefore, we go to the Element and get all annotations on // the parameter. // TODO: doing types.typeAnnotationOf(m.getParameters().get(0).asType(), Nullable.class) // or types.typeAnnotationsOf(m.asType()) // does not work any more. It should. boolean foundNN = false; for (com.sun.tools.javac.code.Attribute.TypeCompound tc : ((com.sun.tools.javac.code.Symbol)m).getRawTypeAttributes()) { if ( tc.position.type == com.sun.tools.javac.code.TargetType.METHOD_FORMAL_PARAMETER && tc.position.parameter_index == 0 && // TODO: using .class would be nicer, but adds a circular dependency on // the "checker" project // tc.type.toString().equals(org.checkerframework.checker.nullness.qual.Nullable.class.getName()) ) { tc.type.toString().equals("org.checkerframework.checker.nullness.qual.Nullable") ) { foundNN = true; } } if (!foundNN) { String jdkJarName = PluginUtil.getJdkJarName(); checker.message(Kind.WARNING, "You do not seem to be using the distributed annotated JDK. To fix the" + System.getProperty("line.separator") + "problem, supply this argument (first, fill in the \"...\") when you run javac:" + System.getProperty("line.separator") + " -Xbootclasspath/p:.../checker/dist/" + jdkJarName); } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy