package org.checkerframework.common.basetype;
/*>>>
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.nullness.qual.Nullable;
*/
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;
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.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
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.BooleanLiteralNode;
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.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.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.type.visitor.SimpleAnnotatedTypeScanner;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.ContractsUtils;
import org.checkerframework.framework.util.ContractsUtils.ConditionalPostcondition;
import org.checkerframework.framework.util.ContractsUtils.Contract;
import org.checkerframework.framework.util.ContractsUtils.Postcondition;
import org.checkerframework.framework.util.ContractsUtils.Precondition;
import org.checkerframework.framework.util.FieldInvariants;
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;
/**
* A {@link SourceVisitor} that performs assignment and pseudo-assignment checking, method
* invocation checking, and assignability checking.
*
* <p>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.
*
* <p>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).
*
* <p>This implementation does the following checks:
*
* <ol>
* <li><b>Assignment and Pseudo-Assignment Check</b>: It verifies that any assignment type-checks,
* using {@code TypeHierarchy.isSubtype} method. This includes method invocation and method
* overriding checks.
* <li><b>Type Validity Check</b>: It verifies that any user-supplied type is a valid type, using
* {@code isValidUse} method.
* <li><b>(Re-)Assignability Check</b>: It verifies that any assignment is valid, using {@code
* Checker.isAssignable} method.
* </ol>
*
* @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<Factory extends GenericAnnotatedTypeFactory<?, ?, ?, ?>>
extends SourceVisitor<Void, Void> {
/** 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;
/**
* @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.
*
* <p>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.
*
* <p>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);
}
/**
* Type-check classTree and skips classes specified by the skipDef option. Subclasses should
* override {@link #processClassTree(ClassTree)} instead of this method.
*
* @param classTree class to check
* @param p null
* @return null
*/
@Override
public final Void visitClass(ClassTree classTree, Void p) {
if (checker.shouldSkipDefs(classTree)) {
// Not "return super.visitClass(classTree, p);" because that would
// recursively call visitors on subtrees; we want to skip the
// class entirely.
return null;
}
atypeFactory.preProcessClassTree(classTree);
AnnotatedDeclaredType preACT = visitorState.getClassType();
ClassTree preCT = visitorState.getClassTree();
AnnotatedDeclaredType preAMT = visitorState.getMethodReceiver();
MethodTree preMT = visitorState.getMethodTree();
Pair<Tree, AnnotatedTypeMirror> preAssCtxt = visitorState.getAssignmentContext();
visitorState.setClassType(atypeFactory.getAnnotatedType(classTree));
visitorState.setClassTree(classTree);
visitorState.setMethodReceiver(null);
visitorState.setMethodTree(null);
visitorState.setAssignmentContext(null);
try {
processClassTree(classTree);
atypeFactory.postProcessClassTree(classTree);
} finally {
this.visitorState.setClassType(preACT);
this.visitorState.setClassTree(preCT);
this.visitorState.setMethodReceiver(preAMT);
this.visitorState.setMethodTree(preMT);
this.visitorState.setAssignmentContext(preAssCtxt);
}
return null;
}
/**
* Type-check classTree. Subclasses should override this method instead of {@link
* #visitClass(ClassTree, Void)}.
*
* @param classTree class to check
*/
public void processClassTree(ClassTree classTree) {
checkFieldInvariantDeclarations(classTree);
if (!TreeUtils.hasExplicitConstructor(classTree)) {
checkDefaultConstructor(classTree);
}
/* Visit the extends and implements clauses.
* The superclass also visits them, but only calls visitParameterizedType, which
* loses a main modifier.
*/
Tree ext = classTree.getExtendsClause();
if (ext != null) {
validateTypeOf(ext);
}
List<? extends Tree> impls = classTree.getImplementsClause();
if (impls != null) {
for (Tree im : impls) {
validateTypeOf(im);
}
}
super.visitClass(classTree, null);
}
/**
* Check that the field invariant declaration annotations meet the following requirements:
*
* <ol>
* <!-- The item numbering is referred to in the body of the method.-->
* <li value="1">If the superclass of {@code classTree} has a field invariant, then the field
* invariant for {@code classTree} must include all the fields in the superclass invariant
* and those fields' annotations must be a subtype (or equal) to the annotations for those
* fields in the superclass.
* <li value="2">The fields in the invariant must be a.) final and b.) declared in a
* superclass of {@code classTree}.
* <li value="3">The qualifier for each field must be a subtype of the annotation on the
* declaration of that field.
* <li value="4">The field invariant has an equal number of fields and qualifiers, or it has
* one qualifier and at least one field.
* </ol>
*
* @param classTree class that might have a field invariant
* @checker_framework.manual #field-invariants Field invariants
*/
protected void checkFieldInvariantDeclarations(ClassTree classTree) {
TypeElement elt = TreeUtils.elementFromDeclaration(classTree);
FieldInvariants invariants = atypeFactory.getFieldInvariants(elt);
if (invariants == null) {
// No invariants to check
return;
}
// Where to issue an error, if any.
Tree errorTree =
atypeFactory.getFieldInvariantAnnotationTree(
classTree.getModifiers().getAnnotations());
if (errorTree == null) {
// If the annotation was inherited, then there is no annotation tree, so issue the
// error on the class.
errorTree = classTree;
}
// Checks #4 (see method Javadoc)
if (!invariants.isWellFormed()) {
checker.report(Result.failure("field.invariant.not.wellformed"), errorTree);
return;
}
TypeMirror superClass = elt.getSuperclass();
List<String> fieldsNotFound = new ArrayList<>(invariants.getFields());
Set<VariableElement> fieldElts =
ElementUtils.findFieldsInTypeOrSuperType(superClass, fieldsNotFound);
// Checks that fields are declared in super class. (#2b)
if (!fieldsNotFound.isEmpty()) {
String notFoundString = PluginUtil.join(", ", fieldsNotFound);
checker.report(Result.failure("field.invariant.not.found", notFoundString), errorTree);
}
FieldInvariants superInvar =
atypeFactory.getFieldInvariants(InternalUtils.getTypeElement(superClass));
if (superInvar != null) {
// Checks #3 (see method Javadoc)
Result superError = invariants.isSuperInvariant(superInvar, atypeFactory);
if (superError != null) {
checker.report(superError, errorTree);
}
}
List<String> notFinal = new ArrayList<>();
for (VariableElement field : fieldElts) {
String fieldName = field.getSimpleName().toString();
if (!ElementUtils.isFinal(field)) {
notFinal.add(fieldName);
}
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(field);
List<AnnotationMirror> annos = invariants.getQualifiersFor(field.getSimpleName());
for (AnnotationMirror invariantAnno : annos) {
AnnotationMirror declaredAnno =
type.getEffectiveAnnotationInHierarchy(invariantAnno);
if (declaredAnno == null) {
// invariant anno isn't in this hierarchy
continue;
}
if (!atypeFactory.getQualifierHierarchy().isSubtype(invariantAnno, declaredAnno)) {
// Checks #3
checker.report(
Result.failure(
"field.invariant.not.subtype",
fieldName,
invariantAnno,
declaredAnno),
errorTree);
}
}
}
// Checks #2a
if (!notFinal.isEmpty()) {
String notFinalString = PluginUtil.join(", ", notFinal);
checker.report(Result.failure("field.invariant.not.final", notFinalString), errorTree);
}
}
protected void checkDefaultConstructor(ClassTree node) {}
/**
* Performs pseudo-assignment check: checks that the method obeys override and subtype rules to
* all overridden methods.
*
* <p>The override rule specifies that a method, m1, may override a method m2 only if:
*
* <ul>
* <li>m1 return type is a subtype of m2
* <li>m1 receiver type is a supertype of m2
* <li>m1 parameters are supertypes of corresponding m2 parameters
* </ul>
*
* 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<K>) 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<Pure.Kind> 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<Pure.Kind> 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);
}
if (atypeFactory.getDependentTypesHelper() != null) {
atypeFactory.getDependentTypesHelper().checkMethod(node, methodType);
}
AnnotatedDeclaredType enclosingType =
(AnnotatedDeclaredType)
atypeFactory.getAnnotatedType(methodElement.getEnclosingElement());
// Find which method this overrides!
Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
AnnotatedTypes.overriddenMethods(elements, atypeFactory, methodElement);
for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> 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);
// check well-formedness of pre/postcondition
List<String> formalParamNames = new ArrayList<String>();
for (VariableTree param : node.getParameters()) {
formalParamNames.add(param.getName().toString());
}
checkContractsAtMethodDeclaration(
node, methodElement, formalParamNames, abstractMethod);
visitorState.setMethodReceiver(preMRT);
visitorState.setMethodTree(preMT);
}
}
/** Reports errors found during purity checking. */
protected void reportPurityErrors(
PurityResult result, MethodTree node, Collection<Pure.Kind> expectedTypes) {
assert !result.isPure(expectedTypes);
Collection<Pure.Kind> 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<Tree, String> 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<Tree, String> 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<Tree, String> r : result.getNotDetReasons()) {
@SuppressWarnings("CompilerMessages")
/*@CompilerMessageKey*/ String msg = "purity.not.deterministic." + r.second;
checker.report(Result.failure(msg), r.first);
}
}
}
}
private void checkContractsAtMethodDeclaration(
MethodTree node,
ExecutableElement methodElement,
List<String> formalParamNames,
boolean abstractMethod) {
FlowExpressionContext flowExprContext = null;
List<Contract> contracts = contractsUtils.getContracts(methodElement);
for (Contract contract : contracts) {
String expression = contract.expression;
AnnotationMirror annotation = contract.annotation;
if (flowExprContext == null) {
flowExprContext =
FlowExpressionContext.buildContextForMethodDeclaration(
node, getCurrentPath(), checker.getContext());
}
FlowExpressions.Receiver expr = null;
try {
expr =
FlowExpressionParseUtil.parse(
expression, flowExprContext, getCurrentPath(), false);
} catch (FlowExpressionParseException e) {
checker.report(e.getResult(), node);
}
if (expr != null && !abstractMethod) {
switch (contract.kind) {
case POSTCONDTION:
checkPostcondition(node, annotation, expr);
break;
case CONDITIONALPOSTCONDTION:
checkConditionalPostcondition(
node,
annotation,
expr,
((ConditionalPostcondition) contract).annoResult);
break;
case PRECONDITION:
// Preconditions are checked at method invocations, not declarations
break;
}
}
if (formalParamNames != null && formalParamNames.contains(expression)) {
@SuppressWarnings("CompilerMessages")
/*@CompilerMessageKey*/ String key =
"contracts." + contract.kind.errorKey + ".expression.parameter.name";
checker.report(
Result.warning(
key,
node.getName().toString(),
expression,
formalParamNames.indexOf(expression) + 1,
expression),
node);
}
checkParametersAreEffectivelyFinal(node, methodElement, expression);
}
}
/**
* Check that the parameters used in {@code stringExpr} are effectively final for method {@code
* method}.
*/
private void checkParametersAreEffectivelyFinal(
MethodTree node, ExecutableElement method, String stringExpr) {
// check that all parameters used in the expression are
// effectively final, so that they cannot be modified
List<Integer> parameterIndices = FlowExpressionParseUtil.parameterIndices(stringExpr);
for (Integer idx : parameterIndices) {
if (idx > method.getParameters().size()) {
// If the index is too big, a parse error was issued in checkContractsAtMethodDeclaration
continue;
}
VariableElement parameter = method.getParameters().get(idx - 1);
if (!ElementUtils.isEffectivelyFinal(parameter)) {
checker.report(
Result.failure("flowexpr.parameter.not.final", "#" + idx, stringExpr),
node);
}
}
}
/**
* Check that the expression's type is annotated with {@code annotation} at the regular exit
* store.
*
* @param methodTree declaration of the method
* @param annotation expression's type must have this annotation
* @param expression the expression that the postcondition concerns
*/
protected void checkPostcondition(
MethodTree methodTree, AnnotationMirror annotation, Receiver expression) {
CFAbstractStore<?, ?> exitStore = atypeFactory.getRegularExitStore(methodTree);
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(expression);
AnnotationMirror inferredAnno = null;
if (value != null) {
QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
Set<AnnotationMirror> annos = value.getAnnotations();
inferredAnno = hierarchy.findAnnotationInSameHierarchy(annos, annotation);
}
if (!checkContract(expression, annotation, inferredAnno, exitStore)) {
checker.report(
Result.failure(
"contracts.postcondition.not.satisfied", expression.toString()),
methodTree);
}
}
}
/**
* Check that the expression's type is annotated with {@code annotation} at every regular exit
* that returns {@code result}
*
* @param node tree of method with the postcondition
* @param annotation expression's type must have this annotation
* @param expression the expression that the postcondition concerns
* @param result result for which the postcondition is valid
*/
protected void checkConditionalPostcondition(
MethodTree node, AnnotationMirror annotation, Receiver expression, boolean result) {
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.
return;
}
for (Pair<ReturnNode, ?> pair : atypeFactory.getReturnStatementStores(node)) {
ReturnNode returnStmt = pair.first;
Node retValNode = returnStmt.getResult();
Boolean retVal =
retValNode instanceof BooleanLiteralNode
? ((BooleanLiteralNode) retValNode).getValue()
: null;
TransferResult<?, ?> transferResult = (TransferResult<?, ?>) pair.second;
if (transferResult == null) {
// Unreachable return statements have no stores, but there is no need to check them.
continue;
}
CFAbstractStore<?, ?> exitStore =
(CFAbstractStore<?, ?>)
(result
? transferResult.getThenStore()
: transferResult.getElseStore());
CFAbstractValue<?> value = exitStore.getValue(expression);
// 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)) {
continue;
}
AnnotationMirror inferredAnno = null;
if (value != null) {
QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
Set<AnnotationMirror> annos = value.getAnnotations();
inferredAnno = hierarchy.findAnnotationInSameHierarchy(annos, annotation);
}
if (!checkContract(expression, annotation, inferredAnno, exitStore)) {
checker.report(
Result.failure(
"contracts.conditional.postcondition.not.satisfied",
expression.toString()),
returnStmt.getTree());
}
}
}
@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<Tree, AnnotatedTypeMirror> preAssCtxt = visitorState.getAssignmentContext();
visitorState.setAssignmentContext(
Pair.of((Tree) node, atypeFactory.getAnnotatedType(node)));
try {
if (atypeFactory.getDependentTypesHelper() != null) {
atypeFactory
.getDependentTypesHelper()
.checkType(visitorState.getAssignmentContext().second, node);
}
// 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)}.
*
* <p>If the subtype check fails, it issues a "assignment.type.incompatible" error.
*/
@Override
public Void visitAssignment(AssignmentTree node, Void p) {
Pair<Tree, AnnotatedTypeMirror> 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.
*
* <p>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.
*
* <p>An invocation of a method, m, on the receiver, r is valid only if:
*
* <ul>
* <li>passed arguments are subtypes of corresponding m parameters
* <li>r is a subtype of m receiver type
* <li>if m is generic, passed type arguments are subtypes of m type variables
* </ul>
*/
@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.elementFromUse(node) == null || TreeUtils.isEnumSuper(node)) {
return super.visitMethodInvocation(node, p);
}
if (shouldSkipUses(node)) {
return super.visitMethodInvocation(node, p);
}
Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> mfuPair =
atypeFactory.methodFromUse(node);
AnnotatedExecutableType invokedMethod = mfuPair.first;
List<AnnotatedTypeMirror> typeargs = mfuPair.second;
List<AnnotatedTypeParameterBounds> paramBounds = new ArrayList<>();
for (AnnotatedTypeVariable param : invokedMethod.getTypeVariables()) {
paramBounds.add(param.getBounds());
}
checkTypeArguments(node, paramBounds, typeargs, node.getTypeArguments());
List<AnnotatedTypeMirror> 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(MethodInvocationTree tree, Set<Precondition> 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;
}
Node node = atypeFactory.getNodeForTree(tree);
FlowExpressionContext flowExprContext =
FlowExpressionContext.buildContextForMethodUse(
(MethodInvocationNode) node, checker.getContext());
if (flowExprContext == null) {
checker.report(Result.failure("flowexpr.parse.context.not.determined", node), tree);
return;
}
for (Precondition p : preconditions) {
String expression = p.expression;
AnnotationMirror anno = p.annotation;
try {
FlowExpressions.Receiver expr =
FlowExpressionParseUtil.parse(
expression, flowExprContext, getCurrentPath(), false);
CFAbstractStore<?, ?> store = atypeFactory.getStoreBefore(node);
CFAbstractValue<?> value = store.getValue(expr);
AnnotationMirror inferredAnno = null;
if (value != null) {
QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
Set<AnnotationMirror> annos = value.getAnnotations();
inferredAnno = hierarchy.findAnnotationInSameHierarchy(annos, anno);
}
if (!checkContract(expr, anno, inferredAnno, store)) {
checker.report(
Result.failure(
"contracts.precondition.not.satisfied",
tree.toString(),
expr == null ? expression : expr.toString()),
tree);
}
} catch (FlowExpressionParseException e) {
// report errors here
checker.report(e.getResult(), tree);
}
}
}
/**
* Returns true if and only if {@code inferredAnnotation} is valid for a given expression to
* match the {@code necessaryAnnotation}.
*
* <p>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()}.
*
* <p>The Checker Framework special-cases the method invocation, as it is type safety cannot be
* expressed by Java's type system.
*
* <p>For a Vector {@code v} of type {@code Vectory<E>}, 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}.
*
* <p>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<? extends AnnotatedTypeMirror> 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 =
AnnotatedTypes.asSuper(atypeFactory, receiver, vectorType);
if (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.
*
* <p>An invocation of a constructor, c, is valid only if:
*
* <ul>
* <li>passed arguments are subtypes of corresponding c parameters
* <li>if c is generic, passed type arguments are subtypes of c type variables
* </ul>
*/
@Override
public Void visitNewClass(NewClassTree node, Void p) {
if (checker.shouldSkipUses(InternalUtils.constructor(node))) {
return super.visitNewClass(node, p);
}
Pair<AnnotatedExecutableType, List<AnnotatedTypeMirror>> fromUse =
atypeFactory.constructorFromUse(node);
AnnotatedExecutableType constructor = fromUse.first;
List<AnnotatedTypeMirror> typeargs = fromUse.second;
List<? extends ExpressionTree> passedArguments = node.getArguments();
List<AnnotatedTypeMirror> params =
AnnotatedTypes.expandVarArgs(atypeFactory, constructor, passedArguments);
checkArguments(params, passedArguments);
List<AnnotatedTypeParameterBounds> 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);
if (atypeFactory.getDependentTypesHelper() != null) {
atypeFactory.getDependentTypesHelper().checkType(dt, node);
}
checkConstructorInvocation(dt, constructor, node);
}
return super.visitNewClass(node, p);
}
@Override
public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
Pair<AnnotatedDeclaredType, AnnotatedExecutableType> 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<Tree, AnnotatedTypeMirror> preAssCtxt = visitorState.getAssignmentContext();
try {
Tree enclosing =
TreeUtils.enclosingOfKind(
getCurrentPath(),
new HashSet<Tree.Kind>(
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<AnnotatedDeclaredType, AnnotatedExecutableType> 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<? extends ExpressionTree> 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<String, AnnotatedTypeMirror> annoTypes = new HashMap<String, AnnotatedTypeMirror>();
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<Tree, AnnotatedTypeMirror> 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.<Tree, AnnotatedTypeMirror>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 varType = atypeFactory.getAnnotatedTypeLhs(node.getExpression());
// For postfix increments/decrements, the value type is incorrect due to the workaround
// in GenericAnnotatedTypeFactory.addComputedTypeAnnotations(Tree, AnnotatedTypeMirror, boolean)
// for the following bug:
// See Issue 867: https://github.com/typetools/checker-framework/issues/867
// This means could result in a false warning (false positive) in some cases and a lack
// of a warning in other cases (false negative).
AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(node);
commonAssignmentCheck(
varType, valueType, node, "compound.assignment.type.incompatible");
}
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) {
AnnotatedArrayType arrayType = atypeFactory.getAnnotatedType(node);
if (atypeFactory.getDependentTypesHelper() != null) {
atypeFactory.getDependentTypesHelper().checkType(arrayType, node);
}
if (node.getInitializers() != null) {
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);
}
if (atypeFactory.getDependentTypesHelper() != null) {
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node);
atypeFactory.getDependentTypesHelper().checkType(type, node.getType());
}
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<Tree, AnnotatedTypeMirror> 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.
*
* <p>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<? extends AnnotationMirror> 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.
*
* <p>Note: by default this method is called by getThrowUpperBoundAnnotations(), so that this
* annotation is enforced.
*
* <p>(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<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotations() {
return atypeFactory.getQualifierHierarchy().getTopAnnotations();
}
/**
* Checks the type of the thrown expression.
*
* <p>By default, this method checks that the thrown expression is a subtype of top.
*
* <p>Issue error if the thrown expression is not a sub type of the annotation given by {@link
* #getThrowUpperBoundAnnotations()}, the same as {@link
* #getExceptionParameterLowerBoundAnnotations()} by default.
*
* <p>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<? extends AnnotationMirror> required = getThrowUpperBoundAnnotations();
switch (throwType.getKind()) {
case NULL:
case DECLARED:
Set<AnnotationMirror> 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<AnnotationMirror> 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<AnnotationMirror> 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.
*
* <p>Note: by default this method is returns by getExceptionParameterLowerBoundAnnotations(),
* so that this annotation is enforced.
*
* <p>(Default is top)
*
* @return set of annotation mirrors, one per hierarchy, that form an upper bound of thrown
* expressions
*/
protected Set<? extends AnnotationMirror> 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 lvalue (usually a variable)
* @param valueExp the AST node for the rvalue (the new 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 lvalue (usually a variable)
* @param valueExp the AST node for the rvalue (the new 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) {
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(),
valueType.toString(),
varType.getKind(),
varType.toString());
}
boolean success = atypeFactory.getTypeHierarchy().isSubtype(valueType, varType);
// TODO: integrate with subtype test.
if (success) {
for (Class<? extends Annotation> 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(),
valueType.toString(),
varType.getKind(),
varType.toString());
}
// Use an error key only if it's overridden by a checker.
if (!success) {
String valueTypeString;
String varTypeString;
if (shouldPrintVerbose(varType, valueType)) {
valueTypeString = valueType.toString(true);
varTypeString = varType.toString(true);
} else {
valueTypeString = valueType.toString();
varTypeString = varType.toString();
}
checker.report(Result.failure(errorKey, valueTypeString, varTypeString), valueTree);
}
}
/**
* Return whether or not the verbose toString should be used when printing the two annotated
* types.
*
* @param atm1 the first AnnotatedTypeMirror
* @param atm2 the second AnnotatedTypeMirror
* @return true iff there are two annotated types (in either ATM) such that their toStrings are
* the same but their verbose toStrings differ
*/
private boolean shouldPrintVerbose(AnnotatedTypeMirror atm1, AnnotatedTypeMirror atm2) {
String atm1ToString = atm1.toString();
String atm2ToString = atm2.toString();
// If both types as strings are the same, use verbose toString.
if (atm2ToString.equals(atm1ToString)
// or if neither string contains an annotation
|| (!atm2ToString.contains("@") && !atm1ToString.contains("@"))) {
return true;
}
SimpleAnnotatedTypeScanner<Boolean, Void> checkForMismatchedToStrings =
new SimpleAnnotatedTypeScanner<Boolean, Void>() {
/** Maps from a type's toString to its verbose toString */
Map<String, String> map = new HashMap<>();
@Override
protected Boolean reduce(Boolean r1, Boolean r2) {
r1 = r1 == null ? false : r1;
r2 = r2 == null ? false : r2;
return r1 || r2;
}
@Override
protected Boolean defaultAction(AnnotatedTypeMirror type, Void avoid) {
if (type == null) {
return false;
}
String simple = type.toString();
String verbose = map.get(simple);
if (verbose == null) {
map.put(simple, type.toString(true));
return false;
} else {
return !verbose.equals(type.toString(true));
}
}
};
Boolean r1 = checkForMismatchedToStrings.visit(atm1);
if (r1 != null && r1) {
return true;
}
// Call reset to clear the visitor history, but not the map from Strings to types.
checkForMismatchedToStrings.reset();
Boolean r2 = checkForMismatchedToStrings.visit(atm2);
// SimpleAnnotatedTypeScanner#scan returns null if it encounters a null AnnotatedTypeMirror.
// This shouldn't happen if the atm1 and atm2 are well-formed.
return r2 == null ? false : r2;
}
protected void checkArrayInitialization(
AnnotatedTypeMirror type, List<? extends ExpressionTree> 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<? extends AnnotatedTypeParameterBounds> paramBounds,
List<? extends AnnotatedTypeMirror> typeargs,
List<? extends Tree> 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<? extends AnnotatedTypeParameterBounds> boundsIter = paramBounds.iterator();
Iterator<? extends AnnotatedTypeMirror> 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<? extends AnnotatedTypeVariable> typevars,
List<? extends AnnotatedTypeMirror> typeargs,
List<? extends Tree> 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<? extends AnnotatedTypeVariable> varIter = typevars.iterator();
Iterator<? extends AnnotatedTypeMirror> argIter = typeargs.iterator();
Iterator<? extends Tree> 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.
*
* <p>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 its 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 = AnnotatedTypes.asSuper(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.
*
* <p>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<? extends AnnotatedTypeMirror> requiredArgs,
List<? extends ExpressionTree> passedArgs) {
assert requiredArgs.size() == passedArgs.size()
: "mismatch between required args ("
+ requiredArgs
+ ") and passed args ("
+ passedArgs
+ ")";
Pair<Tree, AnnotatedTypeMirror> preAssCtxt = visitorState.getAssignmentContext();
try {
for (int i = 0; i < requiredArgs.size(); ++i) {
visitorState.setAssignmentContext(
Pair.<Tree, AnnotatedTypeMirror>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<AnnotatedDeclaredType, AnnotatedExecutableType> result =
atypeFactory.getFnInterfaceFromTree(memberReferenceTree);
AnnotatedDeclaredType overriddenType = result.first;
AnnotatedExecutableType overriddenMethodType = result.second;
// ========= Overriding Type =========
// Get declared type from <expression>::method or <type use>::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 it is. */
private boolean checkMethodReferenceInference(
MemberReferenceTree memberReferenceTree,
AnnotatedExecutableType memberReferenceType,
AnnotatedExecutableType overridden,
AnnotatedTypeMirror overridingType) {
// TODO: Issue #802
// TODO: https://github.com/typetools/checker-framework/issues/802
// 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.
*
* <p>Method references are checked similarly to method overrides, with the method reference
* viewed as overriding the functional interface's method.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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<Pure.Kind> superPurity =
new HashSet<Pure.Kind>(
PurityUtils.getPurityKinds(atypeFactory, overridden.getElement()));
Set<Pure.Kind> subPurity =
new HashSet<Pure.Kind>(
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<Postcondition> superPost = contracts.getPostconditions(overridden.getElement());
Set<Postcondition> subPost = contracts.getPostconditions(overrider.getElement());
Set<Pair<Receiver, AnnotationMirror>> superPost2 =
resolveContracts(superPost, overridden);
Set<Pair<Receiver, AnnotationMirror>> subPost2 = resolveContracts(subPost, overrider);
@SuppressWarnings("CompilerMessages")
/*@CompilerMessageKey*/ String postmsg =
"contracts.postcondition." + msgKey + ".invalid";
checkContractsSubset(
overriderMeth,
overriderTyp,
overriddenMeth,
overriddenTyp,
superPost2,
subPost2,
postmsg);
// Check preconditions
Set<Precondition> superPre = contracts.getPreconditions(overridden.getElement());
Set<Precondition> subPre = contracts.getPreconditions(overrider.getElement());
Set<Pair<Receiver, AnnotationMirror>> superPre2 =
resolveContracts(superPre, overridden);
Set<Pair<Receiver, AnnotationMirror>> 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<ConditionalPostcondition> superCPost =
contracts.getConditionalPostconditions(overridden.getElement());
Set<ConditionalPostcondition> subCPost =
contracts.getConditionalPostconditions(overrider.getElement());
// consider only 'true' postconditions
Set<Postcondition> superCPostTrue = filterConditionalPostconditions(superCPost, true);
Set<Postcondition> subCPostTrue = filterConditionalPostconditions(subCPost, true);
Set<Pair<Receiver, AnnotationMirror>> superCPostTrue2 =
resolveContracts(superCPostTrue, overridden);
Set<Pair<Receiver, AnnotationMirror>> subCPostTrue2 =
resolveContracts(subCPostTrue, overrider);
@SuppressWarnings("CompilerMessages")
/*@CompilerMessageKey*/ String posttruemsg =
"contracts.conditional.postcondition.true." + msgKey + ".invalid";
checkContractsSubset(
overriderMeth,
overriderTyp,
overriddenMeth,
overriddenTyp,
superCPostTrue2,
subCPostTrue2,
posttruemsg);
// consider only 'false' postconditions
Set<Postcondition> superCPostFalse = filterConditionalPostconditions(superCPost, false);
Set<Postcondition> subCPostFalse = filterConditionalPostconditions(subCPost, false);
Set<Pair<Receiver, AnnotationMirror>> superCPostFalse2 =
resolveContracts(superCPostFalse, overridden);
Set<Pair<Receiver, AnnotationMirror>> 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() {
List<AnnotatedTypeMirror> overriderParams = overrider.getParameterTypes();
List<AnnotatedTypeMirror> overriddenParams = overridden.getParameterTypes();
// Fix up method reference parameters.
// See https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.1
if (methodReference) {
// The functional interface of an unbound member reference has an extra parameter (the receiver).
if (((JCTree.JCMemberReference) overriderTree)
.hasKind(JCTree.JCMemberReference.ReferenceKind.UNBOUND)) {
overriddenParams = new ArrayList<>(overriddenParams);
overriddenParams.remove(0);
}
// Deal with varargs
if (overrider.isVarArgs() && !overridden.isVarArgs()) {
overriderParams =
AnnotatedTypes.expandVarArgsFromTypes(overrider, overriddenParams);
}
}
boolean result = true;
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<AnnotatedTypeMirror> overriderParams,
List<AnnotatedTypeMirror> 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 whose annotation result
* value matches the value of the given boolean {@code b}. For example, if {@code b == true},
* then the following {@code @EnsuresNonNullIf} conditional postcondition would match:<br>
* {@code @EnsuresNonNullIf(expression="#1", result=true)}<br>
* {@code boolean equals(@Nullable Object o)}
*/
private Set<Postcondition> filterConditionalPostconditions(
Set<ConditionalPostcondition> conditionalPostconditions, boolean b) {
Set<Postcondition> result = new LinkedHashSet<>();
for (ConditionalPostcondition p : conditionalPostconditions) {
if (p.annoResult == b) {
result.add(new Postcondition(p.expression, p.annotation));
}
}
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<Pair<Receiver, AnnotationMirror>> mustSubset,
Set<Pair<Receiver, AnnotationMirror>> set,
/*@CompilerMessageKey*/ String messageKey) {
for (Pair<Receiver, AnnotationMirror> a : mustSubset) {
boolean found = false;
for (Pair<Receiver, AnnotationMirror> 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<Pair<Receiver, AnnotationMirror>> resolveContracts(
Set<? extends Contract> contractSet, AnnotatedExecutableType method) {
Set<Pair<Receiver, AnnotationMirror>> result = new HashSet<>();
MethodTree methodTree = visitorState.getMethodTree();
TreePath path = atypeFactory.getPath(methodTree);
FlowExpressionContext flowExprContext = null;
for (Contract p : contractSet) {
String expression = p.expression;
AnnotationMirror annotation = p.annotation;
if (flowExprContext == null) {
flowExprContext =
FlowExpressionContext.buildContextForMethodDeclaration(
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, false);
result.add(Pair.of(expr, annotation));
} catch (FlowExpressionParseException e) {
// report errors here
checker.report(e.getResult(), methodTree);
}
}
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
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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<? extends Element> 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);
}
}
}
}
}