blob: 57869f2a945b20da1757d35ec9803fcb4f4cb22f [file] [log] [blame]
package org.checkerframework.common.basetype;
import com.github.javaparser.ParseProblemException;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.printer.DefaultPrettyPrinter;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
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.IntersectionTypeTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
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.code.Symbol.ClassSymbol;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCMemberReference;
import com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind;
import com.sun.tools.javac.tree.TreeInfo;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.Vector;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
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.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.interning.qual.FindDistinct;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.Analysis;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.BooleanLiteralNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.JavaExpressionScanner;
import org.checkerframework.dataflow.expression.LocalVariable;
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.ajava.AnnotationEqualityVisitor;
import org.checkerframework.framework.ajava.ExpectedTreesVisitor;
import org.checkerframework.framework.ajava.InsertAjavaAnnotations;
import org.checkerframework.framework.ajava.JointVisitorWithDefaultAction;
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.DiagMessage;
import org.checkerframework.framework.source.SourceVisitor;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType;
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.AnnotatedIntersectionType;
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.poly.QualifierPolymorphism;
import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.Contract;
import org.checkerframework.framework.util.Contract.ConditionalPostcondition;
import org.checkerframework.framework.util.Contract.Postcondition;
import org.checkerframework.framework.util.Contract.Precondition;
import org.checkerframework.framework.util.ContractsFromMethod;
import org.checkerframework.framework.util.FieldInvariants;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.framework.util.JavaParserUtil;
import org.checkerframework.framework.util.StringToJavaExpression;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.ArraysPlume;
import org.plumelib.util.CollectionsPlume;
/**
* 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
* one of the {@code isValidUse} methods.
* <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;
/** The element for java.util.Vector#copyInto. */
private final ExecutableElement vectorCopyInto;
/** The element for java.util.function.Function#apply. */
private final ExecutableElement functionApply;
/** The type of java.util.Vector. */
private final AnnotatedDeclaredType vectorType;
/** The @java.lang.annotation.Target annotation. */
protected final AnnotationMirror TARGET =
AnnotationBuilder.fromClass(
elements,
java.lang.annotation.Target.class,
AnnotationBuilder.elementNamesValues("value", new ElementType[0]));
/** The {@code value} element/field of the @java.lang.annotation.Target annotation. */
protected final ExecutableElement targetValueElement;
/** The {@code when} element/field of the @Unused annotation. */
protected final ExecutableElement unusedWhenElement;
/** True if "-Ashowchecks" was passed on the command line. */
private final boolean showchecks;
/**
* @param checker the type-checker associated with this visitor (for callbacks to {@link
* TypeHierarchy#isSubtype})
*/
public BaseTypeVisitor(BaseTypeChecker checker) {
this(checker, null);
}
/**
* @param checker the type-checker associated with this visitor
* @param typeFactory the type factory, or null. If null, this calls {@link #createTypeFactory}.
*/
protected BaseTypeVisitor(BaseTypeChecker checker, Factory typeFactory) {
super(checker);
this.checker = checker;
this.atypeFactory = typeFactory == null ? createTypeFactory() : typeFactory;
this.positions = trees.getSourcePositions();
this.visitorState = atypeFactory.getVisitorState();
this.typeValidator = createTypeValidator();
ProcessingEnvironment env = checker.getProcessingEnvironment();
this.vectorCopyInto = TreeUtils.getMethod("java.util.Vector", "copyInto", 1, env);
this.functionApply = TreeUtils.getMethod("java.util.function.Function", "apply", 1, env);
this.vectorType =
atypeFactory.fromElement(elements.getTypeElement(Vector.class.getCanonicalName()));
targetValueElement = TreeUtils.getMethod(Target.class, "value", 0, env);
unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, env);
showchecks = checker.hasOption("showchecks");
}
/**
* 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) {
AnnotatedTypeFactory result =
BaseTypeChecker.invokeConstructorFor(
BaseTypeChecker.getRelatedClassName(checkerClass, "AnnotatedTypeFactory"),
new Class<?>[] {BaseTypeChecker.class},
new Object[] {checker});
if (result != null) {
return (Factory) result;
}
checkerClass = checkerClass.getSuperclass();
}
try {
return (Factory) new BaseAnnotatedTypeFactory(checker);
} catch (Throwable t) {
throw new BugInCF(
"Unexpected "
+ t.getClass().getSimpleName()
+ " when invoking BaseAnnotatedTypeFactory for checker "
+ checker.getClass().getSimpleName(),
t);
}
}
public final Factory getTypeFactory() {
return atypeFactory;
}
/**
* A public variant of {@link #createTypeFactory}. Only use this if you know what you are doing.
*
* @return the appropriate type factory
*/
public Factory createTypeFactoryPublic() {
return createTypeFactory();
}
// **********************************************************************
// Responsible for updating the factory for the location (for performance)
// **********************************************************************
@Override
public void setRoot(CompilationUnitTree root) {
atypeFactory.setRoot(root);
super.setRoot(root);
testJointJavacJavaParserVisitor();
testAnnotationInsertion();
}
@Override
public Void scan(@Nullable Tree tree, Void p) {
if (tree != null && getCurrentPath() != null) {
this.visitorState.setPath(new TreePath(getCurrentPath(), tree));
}
return super.scan(tree, p);
}
/**
* Test {@link org.checkerframework.framework.ajava.JointJavacJavaParserVisitor} if the checker
* has the "ajavaChecks" option.
*
* <p>Parse the current source file with JavaParser and check that the AST can be matched with the
* Tree prodoced by javac. Crash if not.
*
* <p>Subclasses may override this method to disable the test if even the option is provided.
*/
protected void testJointJavacJavaParserVisitor() {
if (root == null || !checker.hasOption("ajavaChecks")) {
return;
}
Map<Tree, com.github.javaparser.ast.Node> treePairs = new HashMap<>();
try (InputStream reader = root.getSourceFile().openInputStream()) {
CompilationUnit javaParserRoot = JavaParserUtil.parseCompilationUnit(reader);
JavaParserUtil.concatenateAddedStringLiterals(javaParserRoot);
new JointVisitorWithDefaultAction() {
@Override
public void defaultAction(Tree javacTree, com.github.javaparser.ast.Node javaParserNode) {
treePairs.put(javacTree, javaParserNode);
}
}.visitCompilationUnit(root, javaParserRoot);
ExpectedTreesVisitor expectedTreesVisitor = new ExpectedTreesVisitor();
expectedTreesVisitor.visitCompilationUnit(root, null);
for (Tree expected : expectedTreesVisitor.getTrees()) {
if (!treePairs.containsKey(expected)) {
throw new BugInCF(
"Javac tree not matched to JavaParser node: %s, in file: %s",
expected, root.getSourceFile().getName());
}
}
} catch (IOException e) {
throw new BugInCF("Error reading Java source file", e);
}
}
/**
* Tests {@link org.checkerframework.framework.ajava.InsertAjavaAnnotations} if the checker has
* the "ajavaChecks" option.
*
* <ol>
* <li>Parses the current file with JavaParser.
* <li>Removes all annotations.
* <li>Reinserts the annotations.
* <li>Throws an exception if the ASTs are not the same.
* </ol>
*
* <p>Subclasses may override this method to disable the test even if the option is provided.
*/
protected void testAnnotationInsertion() {
if (root == null || !checker.hasOption("ajavaChecks")) {
return;
}
CompilationUnit originalAst;
try (InputStream originalInputStream = root.getSourceFile().openInputStream()) {
originalAst = JavaParserUtil.parseCompilationUnit(originalInputStream);
} catch (IOException e) {
throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e);
}
CompilationUnit astWithoutAnnotations = originalAst.clone();
JavaParserUtil.clearAnnotations(astWithoutAnnotations);
String withoutAnnotations = new DefaultPrettyPrinter().print(astWithoutAnnotations);
String withAnnotations;
try (InputStream annotationInputStream = root.getSourceFile().openInputStream()) {
// This check only runs on files from the Checker Framework test suite, which should all use
// UNIX line separators. Using System.lineSeparator instead of "\n" could cause the test to
// fail on Mac or Windows.
withAnnotations =
new InsertAjavaAnnotations(elements)
.insertAnnotations(annotationInputStream, withoutAnnotations, "\n");
} catch (IOException e) {
throw new BugInCF("Error while reading Java file: " + root.getSourceFile().toUri(), e);
}
CompilationUnit modifiedAst = null;
try {
modifiedAst = JavaParserUtil.parseCompilationUnit(withAnnotations);
} catch (ParseProblemException e) {
throw new BugInCF("Failed to parse annotation insertion:\n" + withAnnotations, e);
}
AnnotationEqualityVisitor visitor = new AnnotationEqualityVisitor();
originalAst.accept(visitor, modifiedAst);
if (!visitor.getAnnotationsMatch()) {
throw new BugInCF(
"Reinserting annotations produced different AST.\n"
+ "Original node: "
+ visitor.getMismatchedNode1()
+ "\n"
+ "Node with annotations re-inserted: "
+ visitor.getMismatchedNode2()
+ "\n"
+ "Original annotations: "
+ visitor.getMismatchedNode1().getAnnotations()
+ "\n"
+ "Re-inserted annotations: "
+ visitor.getMismatchedNode2().getAnnotations()
+ "\n"
+ "Original AST:\n"
+ originalAst
+ "\n"
+ "Ast with annotations re-inserted: "
+ modifiedAst);
}
}
/**
* 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);
TreePath preTreePath = visitorState.getPath();
AnnotatedDeclaredType preACT = visitorState.getClassType();
ClassTree preCT = visitorState.getClassTree();
AnnotatedDeclaredType preAMT = visitorState.getMethodReceiver();
MethodTree preMT = visitorState.getMethodTree();
Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
// Don't use atypeFactory.getPath, because that depends on the visitorState path.
visitorState.setPath(TreePath.getPath(root, classTree));
visitorState.setClassType(
atypeFactory.getAnnotatedType(TreeUtils.elementFromDeclaration(classTree)));
visitorState.setClassTree(classTree);
visitorState.setMethodReceiver(null);
visitorState.setMethodTree(null);
visitorState.setAssignmentContext(null);
try {
processClassTree(classTree);
atypeFactory.postProcessClassTree(classTree);
} finally {
visitorState.setPath(preTreePath);
visitorState.setClassType(preACT);
visitorState.setClassTree(preCT);
visitorState.setMethodReceiver(preAMT);
visitorState.setMethodTree(preMT);
visitorState.setAssignmentContext(preAssignmentContext);
}
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);
}
AnnotatedDeclaredType classType = atypeFactory.getAnnotatedType(classTree);
atypeFactory.getDependentTypesHelper().checkClassForErrorExpressions(classTree, classType);
validateType(classTree, classType);
Tree ext = classTree.getExtendsClause();
if (ext != null) {
validateTypeOf(ext);
}
List<? extends Tree> impls = classTree.getImplementsClause();
if (impls != null) {
for (Tree im : impls) {
validateTypeOf(im);
}
}
checkForPolymorphicQualifiers(classTree);
checkExtendsImplements(classTree);
checkQualifierParameter(classTree);
super.visitClass(classTree, null);
}
/**
* A TreeScanner that issues an "invalid.polymorphic.qualifier" error for each {@link
* AnnotationTree} that is a polymorphic qualifier. The second parameter is added to the error
* message and should explain the location.
*/
private final TreeScanner<Void, String> polyTreeScanner =
new TreeScanner<Void, String>() {
@Override
public Void visitAnnotation(AnnotationTree annoTree, String location) {
QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(annoTree);
if (atypeFactory.isSupportedQualifier(anno)
&& qualifierHierarchy.isPolymorphicQualifier(anno)) {
checker.reportError(annoTree, "invalid.polymorphic.qualifier", anno, location);
}
return super.visitAnnotation(annoTree, location);
}
};
/**
* Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on the
* class declaration.
*
* @param classTree the class to check
*/
protected void checkForPolymorphicQualifiers(ClassTree classTree) {
if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) {
// Anonymous class can have polymorphic annotations, so don't check them.
return;
}
classTree.getModifiers().accept(polyTreeScanner, "in a class declaration");
if (classTree.getExtendsClause() != null) {
classTree.getExtendsClause().accept(polyTreeScanner, "in a class declaration");
}
for (Tree tree : classTree.getImplementsClause()) {
tree.accept(polyTreeScanner, "in a class declaration");
}
for (Tree tree : classTree.getTypeParameters()) {
tree.accept(polyTreeScanner, "in a class declaration");
}
}
/**
* Issues an "invalid.polymorphic.qualifier" error for all polymorphic annotations written on the
* type parameters declaration.
*
* @param typeParameterTrees the type parameters to check
*/
protected void checkForPolymorphicQualifiers(
List<? extends TypeParameterTree> typeParameterTrees) {
for (Tree tree : typeParameterTrees) {
tree.accept(polyTreeScanner, "in a type parameter");
}
}
/**
* Issues an error if {@code classTree} has polymorphic fields but is not annotated with
* {@code @HasQualifierParameter}. Always issue a warning if the type of a static field is
* annotated with a polymorphic qualifier.
*
* <p>Issues an error if {@code classTree} extends or implements a class/interface that has a
* qualifier parameter, but this class does not.
*
* @param classTree the ClassTree to check for polymorphic fields
*/
protected void checkQualifierParameter(ClassTree classTree) {
// Set of polymorphic qualifiers for hierarchies that do not have a qualifier parameter and
// therefor cannot appear on a field.
Set<AnnotationMirror> illegalOnFieldsPolyQual = AnnotationUtils.createAnnotationSet();
// Set of polymorphic annotations for all hierarchies
Set<AnnotationMirror> polys = AnnotationUtils.createAnnotationSet();
TypeElement classElement = TreeUtils.elementFromDeclaration(classTree);
for (AnnotationMirror top : atypeFactory.getQualifierHierarchy().getTopAnnotations()) {
AnnotationMirror poly = atypeFactory.getQualifierHierarchy().getPolymorphicAnnotation(top);
if (poly != null) {
polys.add(poly);
}
// else {
// If there is no polymorphic qualifier in the hierarchy, it could still have a
// @HasQualifierParameter that must be checked.
// }
if (atypeFactory.hasExplicitQualifierParameterInHierarchy(classElement, top)
&& atypeFactory.hasExplicitNoQualifierParameterInHierarchy(classElement, top)) {
checker.reportError(classTree, "conflicting.qual.param", top);
}
if (atypeFactory.hasQualifierParameterInHierarchy(classElement, top)) {
continue;
}
if (poly != null) {
illegalOnFieldsPolyQual.add(poly);
}
Element extendsEle = TypesUtils.getTypeElement(classElement.getSuperclass());
if (extendsEle != null && atypeFactory.hasQualifierParameterInHierarchy(extendsEle, top)) {
checker.reportError(classTree, "missing.has.qual.param", top);
} else {
for (TypeMirror interfaceType : classElement.getInterfaces()) {
Element interfaceEle = TypesUtils.getTypeElement(interfaceType);
if (atypeFactory.hasQualifierParameterInHierarchy(interfaceEle, top)) {
checker.reportError(classTree, "missing.has.qual.param", top);
break; // only issue error once
}
}
}
}
for (Tree mem : classTree.getMembers()) {
if (mem.getKind() == Tree.Kind.VARIABLE) {
AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(mem);
List<DiagMessage> hasIllegalPoly;
if (ElementUtils.isStatic(TreeUtils.elementFromDeclaration((VariableTree) mem))) {
// A polymorphic qualifier is not allowed on a static field even if the class
// has a qualifier parameter.
hasIllegalPoly = polyScanner.visit(fieldType, polys);
} else {
hasIllegalPoly = polyScanner.visit(fieldType, illegalOnFieldsPolyQual);
}
for (DiagMessage dm : hasIllegalPoly) {
checker.report(mem, dm);
}
}
}
}
/**
* A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a use
* of one of the polymorphic qualifiers.
*/
private final PolyTypeScanner polyScanner = new PolyTypeScanner();
/**
* A scanner that given a set of polymorphic qualifiers, returns a list of errors reporting a use
* of one of the polymorphic qualifiers.
*/
static class PolyTypeScanner
extends SimpleAnnotatedTypeScanner<List<DiagMessage>, Set<AnnotationMirror>> {
/** Create PolyTypeScanner. */
private PolyTypeScanner() {
super(DiagMessage::mergeLists, Collections.emptyList());
}
@Override
protected List<DiagMessage> defaultAction(
AnnotatedTypeMirror type, Set<AnnotationMirror> polys) {
if (type == null) {
return Collections.emptyList();
}
for (AnnotationMirror poly : polys) {
if (type.hasAnnotationRelaxed(poly)) {
return Collections.singletonList(
new DiagMessage(Kind.ERROR, "invalid.polymorphic.qualifier.use", poly));
}
}
return Collections.emptyList();
}
}
/**
* If "@B class Y extends @A X {}", then enforce that @B must be a subtype of @A.
*
* <p>Also validate the types of the extends and implements clauses.
*
* @param classTree class tree to check
*/
protected void checkExtendsImplements(ClassTree classTree) {
if (TypesUtils.isAnonymous(TreeUtils.typeOf(classTree))) {
// Don't check extends clause on anonymous classes.
return;
}
Set<AnnotationMirror> classBounds =
atypeFactory.getTypeDeclarationBounds(TreeUtils.typeOf(classTree));
QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
// If "@B class Y extends @A X {}", then enforce that @B must be a subtype of @A.
// classTree.getExtendsClause() is null when there is no explicitly-written extends clause,
// as in "class X {}". This is equivalent to writing "class X extends @Top Object {}", so
// there is no need to do any subtype checking.
if (classTree.getExtendsClause() != null) {
Set<AnnotationMirror> extendsAnnos =
atypeFactory.getTypeOfExtendsImplements(classTree.getExtendsClause()).getAnnotations();
for (AnnotationMirror classAnno : classBounds) {
AnnotationMirror extendsAnno =
qualifierHierarchy.findAnnotationInSameHierarchy(extendsAnnos, classAnno);
if (!qualifierHierarchy.isSubtype(classAnno, extendsAnno)) {
checker.reportError(
classTree.getExtendsClause(),
"declaration.inconsistent.with.extends.clause",
classAnno,
extendsAnno);
}
}
}
// Do the same check as above for implements clauses.
for (Tree implementsClause : classTree.getImplementsClause()) {
Set<AnnotationMirror> implementsClauseAnnos =
atypeFactory.getTypeOfExtendsImplements(implementsClause).getAnnotations();
for (AnnotationMirror classAnno : classBounds) {
AnnotationMirror implementsAnno =
qualifierHierarchy.findAnnotationInSameHierarchy(implementsClauseAnnos, classAnno);
if (!qualifierHierarchy.isSubtype(classAnno, implementsAnno)) {
checker.reportError(
implementsClause,
"declaration.inconsistent.with.implements.clause",
classAnno,
implementsAnno);
}
}
}
}
/**
* 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.reportError(errorTree, "field.invariant.not.wellformed");
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 = String.join(", ", fieldsNotFound);
checker.reportError(errorTree, "field.invariant.not.found", notFoundString);
}
FieldInvariants superInvar =
atypeFactory.getFieldInvariants(TypesUtils.getTypeElement(superClass));
if (superInvar != null) {
// Checks #3 (see method Javadoc)
DiagMessage superError = invariants.isSuperInvariant(superInvar, atypeFactory);
if (superError != null) {
checker.report(errorTree, superError);
}
}
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.reportError(
errorTree, "field.invariant.not.subtype", fieldName, invariantAnno, declaredAnno);
}
}
}
// Checks #2a
if (!notFinal.isEmpty()) {
String notFinalString = String.join(", ", notFinal);
checker.reportError(errorTree, "field.invariant.not.final", notFinalString);
}
}
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);
warnAboutTypeAnnotationsTooEarly(node, node.getModifiers());
if (node.getReturnType() != null) {
visitAnnotatedType(node.getModifiers().getAnnotations(), node.getReturnType());
}
try {
if (TreeUtils.isAnonymousConstructor(node)) {
// We shouldn't dig deeper
return null;
}
if (TreeUtils.isConstructor(node)) {
checkConstructorResult(methodType, methodElement);
}
checkPurity(node);
// Passing the whole method/constructor validates the return type
validateTypeOf(node);
// Validate types in throws clauses
for (ExpressionTree thr : node.getThrows()) {
validateTypeOf(thr);
}
atypeFactory.getDependentTypesHelper().checkMethodForErrorExpressions(node, methodType);
// Check method overrides
AnnotatedDeclaredType enclosingType =
(AnnotatedDeclaredType)
atypeFactory.getAnnotatedType(methodElement.getEnclosingElement());
// Find which methods this method overrides
Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
AnnotatedTypes.overriddenMethods(elements, atypeFactory, methodElement);
for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair :
overriddenMethods.entrySet()) {
AnnotatedDeclaredType overriddenType = pair.getKey();
ExecutableElement overriddenMethodElt = pair.getValue();
AnnotatedExecutableType overriddenMethodType =
AnnotatedTypes.asMemberOf(types, atypeFactory, overriddenType, overriddenMethodElt);
if (!checkOverride(node, enclosingType, overriddenMethodType, overriddenType)) {
// 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;
}
}
// Check well-formedness of pre/postcondition
boolean abstractMethod =
methodElement.getModifiers().contains(Modifier.ABSTRACT)
|| methodElement.getModifiers().contains(Modifier.NATIVE);
List<String> formalParamNames =
CollectionsPlume.mapList(
(VariableTree param) -> param.getName().toString(), node.getParameters());
checkContractsAtMethodDeclaration(node, methodElement, formalParamNames, abstractMethod);
// Infer postconditions
if (atypeFactory.getWholeProgramInference() != null) {
assert ElementUtils.isElementFromSourceCode(methodElement);
// TODO: Infer conditional postconditions too.
CFAbstractStore<?, ?> store = atypeFactory.getRegularExitStore(node);
// The store is null if the method has no normal exit, for example if its body is a
// throw statement.
if (store != null) {
atypeFactory
.getWholeProgramInference()
.updateContracts(Analysis.BeforeOrAfter.AFTER, methodElement, store);
}
}
checkForPolymorphicQualifiers(node.getTypeParameters());
return super.visitMethod(node, p);
} finally {
visitorState.setMethodReceiver(preMRT);
visitorState.setMethodTree(preMT);
}
}
/**
* Check method purity if needed. Note that overriding rules are checked as part of {@link
* #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType,
* AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType,
* AnnotatedTypeMirror.AnnotatedDeclaredType)}.
*
* @param node the method tree to check
*/
protected void checkPurity(MethodTree node) {
if (!checker.hasOption("checkPurityAnnotations")) {
return;
}
boolean anyPurityAnnotation = PurityUtils.hasPurityAnnotation(atypeFactory, node);
boolean suggestPureMethods = checker.hasOption("suggestPureMethods");
if (!anyPurityAnnotation && !suggestPureMethods) {
return;
}
// check "no" purity
EnumSet<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.reportWarning(node, "purity.deterministic.constructor");
} else if (TreeUtils.typeOf(node.getReturnType()).getKind() == TypeKind.VOID) {
checker.reportWarning(node, "purity.deterministic.void.method");
}
}
TreePath body = atypeFactory.getPath(node.getBody());
PurityResult r;
if (body == null) {
r = new PurityResult();
} else {
r =
PurityChecker.checkPurity(
body,
atypeFactory,
checker.hasOption("assumeSideEffectFree") || checker.hasOption("assumePure"),
checker.hasOption("assumeDeterministic") || checker.hasOption("assumePure"));
}
if (!r.isPure(kinds)) {
reportPurityErrors(r, node, kinds);
}
if (suggestPureMethods && !TreeUtils.isSynthetic(node)) {
// Issue a warning if the method is pure, but not annotated as such.
EnumSet<Pure.Kind> additionalKinds = r.getKinds().clone();
additionalKinds.removeAll(kinds);
if (TreeUtils.isConstructor(node)) {
additionalKinds.remove(Pure.Kind.DETERMINISTIC);
}
if (!additionalKinds.isEmpty()) {
if (additionalKinds.size() == 2) {
checker.reportWarning(node, "purity.more.pure", node.getName());
} else if (additionalKinds.contains(Pure.Kind.SIDE_EFFECT_FREE)) {
checker.reportWarning(node, "purity.more.sideeffectfree", node.getName());
} else if (additionalKinds.contains(Pure.Kind.DETERMINISTIC)) {
checker.reportWarning(node, "purity.more.deterministic", node.getName());
} else {
assert false : "BaseTypeVisitor reached undesirable state";
}
}
}
}
/**
* Issue a warning if the result type of the constructor is not top. If it is a supertype of the
* class, then a conflicting.annos error will also be issued by {@link
* #isValidUse(AnnotatedTypeMirror.AnnotatedDeclaredType,AnnotatedTypeMirror.AnnotatedDeclaredType,Tree)}.
*
* @param constructorType AnnotatedExecutableType for the constructor
* @param constructorElement element that declares the constructor
*/
protected void checkConstructorResult(
AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
Set<AnnotationMirror> constructorAnnotations = constructorType.getReturnType().getAnnotations();
Set<? extends AnnotationMirror> tops = qualifierHierarchy.getTopAnnotations();
for (AnnotationMirror top : tops) {
AnnotationMirror constructorAnno =
qualifierHierarchy.findAnnotationInHierarchy(constructorAnnotations, top);
if (!qualifierHierarchy.isSubtype(top, constructorAnno)) {
checker.reportWarning(
constructorElement, "inconsistent.constructor.type", constructorAnno, top);
}
}
}
/**
* Reports errors found during purity checking.
*
* @param result whether the method is deterministic and/or side-effect-free
* @param node the method
* @param expectedKinds the expected purity for the method
*/
protected void reportPurityErrors(
PurityResult result, MethodTree node, EnumSet<Pure.Kind> expectedKinds) {
assert !result.isPure(expectedKinds);
EnumSet<Pure.Kind> violations = EnumSet.copyOf(expectedKinds);
violations.removeAll(result.getKinds());
if (violations.contains(Pure.Kind.DETERMINISTIC)
|| violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) {
String msgKeyPrefix;
if (!violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) {
msgKeyPrefix = "purity.not.deterministic.";
} else if (!violations.contains(Pure.Kind.DETERMINISTIC)) {
msgKeyPrefix = "purity.not.sideeffectfree.";
} else {
msgKeyPrefix = "purity.not.deterministic.not.sideeffectfree.";
}
for (Pair<Tree, String> r : result.getNotBothReasons()) {
reportPurityError(msgKeyPrefix, r);
}
if (violations.contains(Pure.Kind.SIDE_EFFECT_FREE)) {
for (Pair<Tree, String> r : result.getNotSEFreeReasons()) {
reportPurityError("purity.not.sideeffectfree.", r);
}
}
if (violations.contains(Pure.Kind.DETERMINISTIC)) {
for (Pair<Tree, String> r : result.getNotDetReasons()) {
reportPurityError("purity.not.deterministic.", r);
}
}
}
}
/**
* Reports a single purity error.
*
* @param msgKeyPrefix the prefix of the message key to use when reporting
* @param r the result to report
*/
private void reportPurityError(String msgKeyPrefix, Pair<Tree, String> r) {
String reason = r.second;
@SuppressWarnings("compilermessages")
@CompilerMessageKey String msgKey = msgKeyPrefix + reason;
if (reason.equals("call")) {
if (r.first.getKind() == Tree.Kind.METHOD_INVOCATION) {
MethodInvocationTree mitree = (MethodInvocationTree) r.first;
checker.reportError(r.first, msgKey, mitree.getMethodSelect());
} else {
NewClassTree nctree = (NewClassTree) r.first;
checker.reportError(r.first, msgKey, nctree.getIdentifier());
}
} else {
checker.reportError(r.first, msgKey);
}
}
/**
* Check the contracts written on a method declaration. Ensures that the postconditions hold on
* exit, and that the contracts are well-formed.
*
* @param methodTree the method declaration
* @param methodElement the method element
* @param formalParamNames the formal parameter names
* @param abstractMethod whether the method is abstract
*/
private void checkContractsAtMethodDeclaration(
MethodTree methodTree,
ExecutableElement methodElement,
List<String> formalParamNames,
boolean abstractMethod) {
Set<Contract> contracts = atypeFactory.getContractsFromMethod().getContracts(methodElement);
if (contracts.isEmpty()) {
return;
}
StringToJavaExpression stringToJavaExpr =
stringExpr -> StringToJavaExpression.atMethodBody(stringExpr, methodTree, checker);
for (Contract contract : contracts) {
String expressionString = contract.expressionString;
AnnotationMirror annotation =
contract.viewpointAdaptDependentTypeAnnotation(
atypeFactory, stringToJavaExpr, methodTree);
JavaExpression exprJe;
try {
exprJe = StringToJavaExpression.atMethodBody(expressionString, methodTree, checker);
} catch (JavaExpressionParseException e) {
DiagMessage diagMessage = e.getDiagMessage();
if (diagMessage.getMessageKey().equals("flowexpr.parse.error")) {
String s =
String.format(
"'%s' in the %s %s on the declaration of method '%s': ",
expressionString,
contract.kind.errorKey,
contract.contractAnnotation.getAnnotationType().asElement().getSimpleName(),
methodTree.getName().toString());
checker.reportError(methodTree, "flowexpr.parse.error", s + diagMessage.getArgs()[0]);
} else {
checker.report(methodTree, e.getDiagMessage());
}
continue;
}
if (!CFAbstractStore.canInsertJavaExpression(exprJe)) {
checker.reportError(methodTree, "flowexpr.parse.error", expressionString);
continue;
}
if (!abstractMethod && contract.kind != Contract.Kind.PRECONDITION) {
// Check the contract, which is a postcondition.
// Preconditions are checked at method invocations, not declarations.
switch (contract.kind) {
case POSTCONDITION:
checkPostcondition(methodTree, annotation, exprJe);
break;
case CONDITIONALPOSTCONDITION:
checkConditionalPostcondition(
methodTree, annotation, exprJe, ((ConditionalPostcondition) contract).resultValue);
break;
default:
throw new BugInCF("Impossible: " + contract.kind);
}
}
if (formalParamNames != null && formalParamNames.contains(expressionString)) {
String locationOfExpression =
contract.kind.errorKey
+ " "
+ contract.contractAnnotation.getAnnotationType().asElement().getSimpleName()
+ " on the declaration";
checker.reportWarning(
methodTree,
"expression.parameter.name.shadows.field",
locationOfExpression,
methodTree.getName().toString(),
expressionString,
expressionString,
formalParamNames.indexOf(expressionString) + 1);
}
checkParametersAreEffectivelyFinal(methodTree, exprJe);
}
}
/**
* Scans a {@link JavaExpression} and adds all the parameters in the {@code JavaExpression} to the
* passed set.
*/
private final JavaExpressionScanner<Set<Element>> findParameters =
new JavaExpressionScanner<Set<Element>>() {
@Override
protected Void visitLocalVariable(LocalVariable localVarExpr, Set<Element> parameters) {
if (localVarExpr.getElement().getKind() == ElementKind.PARAMETER) {
parameters.add(localVarExpr.getElement());
}
return super.visitLocalVariable(localVarExpr, parameters);
}
};
/**
* Check that the parameters used in {@code javaExpression} are effectively final for method
* {@code method}.
*
* @param methodDeclTree a method declaration
* @param javaExpression a Java expression
*/
private void checkParametersAreEffectivelyFinal(
MethodTree methodDeclTree, JavaExpression javaExpression) {
// check that all parameters used in the expression are
// effectively final, so that they cannot be modified
Set<Element> parameters = new HashSet<>(1);
findParameters.scan(javaExpression, parameters);
for (Element parameter : parameters) {
if (!ElementUtils.isEffectivelyFinal(parameter)) {
checker.reportError(
methodDeclTree,
"flowexpr.parameter.not.final",
parameter.getSimpleName(),
javaExpression);
}
}
}
/**
* 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 must have an annotation
*/
protected void checkPostcondition(
MethodTree methodTree, AnnotationMirror annotation, JavaExpression 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.reportError(
methodTree,
"contracts.postcondition",
methodTree.getName(),
contractExpressionAndType(expression.toString(), inferredAnno),
contractExpressionAndType(expression.toString(), annotation));
}
}
}
/**
* Returns a string representation of an expression and type qualifier.
*
* @param expression a Java expression
* @param qualifier the expression's type, or null if no information is available
* @return a string representation of the expression and type qualifier
*/
private String contractExpressionAndType(
String expression, @Nullable AnnotationMirror qualifier) {
if (qualifier == null) {
return "no information about " + expression;
} else {
return expression
+ " is "
+ atypeFactory.getAnnotationFormatter().formatAnnotationMirror(qualifier);
}
}
/**
* Check that the expression's type is annotated with {@code annotation} at every regular exit
* that returns {@code result}.
*
* @param methodTree 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 methodTree,
AnnotationMirror annotation,
JavaExpression expression,
boolean result) {
boolean booleanReturnType =
TypesUtils.isBooleanType(TreeUtils.typeOf(methodTree.getReturnType()));
if (!booleanReturnType) {
checker.reportError(methodTree, "contracts.conditional.postcondition.returntype");
// No reason to go ahead with further checking. The
// annotation is invalid.
return;
}
for (Pair<ReturnNode, ?> pair : atypeFactory.getReturnStatementStores(methodTree)) {
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.reportError(
returnStmt.getTree(),
"contracts.conditional.postcondition",
methodTree.getName(),
result,
contractExpressionAndType(expression.toString(), inferredAnno),
contractExpressionAndType(expression.toString(), annotation));
}
}
}
@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);
}
if (node.getBounds().size() > 1) {
// The upper bound of the type parameter is an intersection
AnnotatedTypeVariable type =
(AnnotatedTypeVariable) atypeFactory.getAnnotatedTypeFromTypeTree(node);
AnnotatedIntersectionType intersection = (AnnotatedIntersectionType) type.getUpperBound();
checkExplicitAnnotationsOnIntersectionBounds(intersection, node.getBounds());
}
return super.visitTypeParameter(node, p);
}
/**
* Issues "explicit.annotation.ignored" warning if any explicit annotation on an intersection
* bound is not the same as the primary annotation of the given intersection type.
*
* @param intersection type to use
* @param boundTrees trees of {@code intersection} bounds
*/
protected void checkExplicitAnnotationsOnIntersectionBounds(
AnnotatedIntersectionType intersection, List<? extends Tree> boundTrees) {
for (Tree boundTree : boundTrees) {
if (boundTree.getKind() != Tree.Kind.ANNOTATED_TYPE) {
continue;
}
List<? extends AnnotationMirror> explictAnnos =
TreeUtils.annotationsFromTree((AnnotatedTypeTree) boundTree);
for (AnnotationMirror explictAnno : explictAnnos) {
if (atypeFactory.isSupportedQualifier(explictAnno)) {
AnnotationMirror anno = intersection.getAnnotationInHierarchy(explictAnno);
if (!AnnotationUtils.areSame(anno, explictAnno)) {
checker.reportWarning(
boundTree, "explicit.annotation.ignored", explictAnno, anno, explictAnno, anno);
}
}
}
}
}
// **********************************************************************
// Assignment checkers and pseudo-assignments
// **********************************************************************
@Override
public Void visitVariable(VariableTree node, Void p) {
warnAboutTypeAnnotationsTooEarly(node, node.getModifiers());
visitAnnotatedType(node.getModifiers().getAnnotations(), node.getType());
Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
AnnotatedTypeMirror variableType;
if (getCurrentPath().getParentPath() != null
&& getCurrentPath().getParentPath().getLeaf().getKind() == Tree.Kind.LAMBDA_EXPRESSION) {
// Calling getAnnotatedTypeLhs on a lambda parameter node is possibly expensive
// because caching is turned off. This should be fixed by #979.
// See https://github.com/typetools/checker-framework/issues/2853 for an example.
variableType = atypeFactory.getAnnotatedType(node);
} else {
variableType = atypeFactory.getAnnotatedTypeLhs(node);
}
visitorState.setAssignmentContext(Pair.of(node, variableType));
try {
atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(variableType, node);
// If there's no assignment in this variable declaration, skip it.
if (node.getInitializer() != null) {
commonAssignmentCheck(node, node.getInitializer(), "assignment");
} 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(preAssignmentContext);
}
}
/**
* Warn if a type annotation is written before a modifier such as "public" or before a declaration
* annotation.
*
* @param node a VariableTree or a MethodTree
* @param modifiersTree the modifiers sub-tree of node
*/
private void warnAboutTypeAnnotationsTooEarly(Tree node, ModifiersTree modifiersTree) {
// Don't issue warnings about compiler-inserted modifiers.
// This simple code completely igonores enum constants and try-with-resources declarations.
// It could be made to catch some user errors in those locations, but it doesn't seem worth
// the effort to do so.
if (node.getKind() == Tree.Kind.VARIABLE) {
ElementKind varKind = TreeUtils.elementFromDeclaration((VariableTree) node).getKind();
switch (varKind) {
case ENUM_CONSTANT:
// Enum constants are "public static final" by default, so the annotation always
// appears to be before "public".
return;
case RESOURCE_VARIABLE:
// Try-with-resources variables are "final" by default, so the annotation always
// appears to be before "final".
return;
default:
// Nothing to do
}
}
Set<Modifier> modifierSet = modifiersTree.getFlags();
List<? extends AnnotationTree> annotations = modifiersTree.getAnnotations();
if (annotations.isEmpty()) {
return;
}
// Warn about type annotations written before modifiers such as "public". javac retains no
// information about modifier locations. So, this is a very partial check: Issue a warning if
// a type annotation is at the very beginning of the VariableTree, and a modifier follows it.
// Check if a type annotation precedes a declaration annotation.
int lastDeclAnnoIndex = -1;
for (int i = annotations.size() - 1; i > 0; i--) { // no need to check index 0
if (!isTypeAnnotation(annotations.get(i))) {
lastDeclAnnoIndex = i;
break;
}
}
if (lastDeclAnnoIndex != -1) {
List<AnnotationTree> badTypeAnnos = new ArrayList<>();
for (int i = 0; i < lastDeclAnnoIndex; i++) {
AnnotationTree anno = annotations.get(i);
if (isTypeAnnotation(anno)) {
badTypeAnnos.add(anno);
}
}
if (!badTypeAnnos.isEmpty()) {
checker.reportWarning(
node, "type.anno.before.decl.anno", badTypeAnnos, annotations.get(lastDeclAnnoIndex));
}
}
// Determine the length of the text that ought to precede the first type annotation.
// If the type annotation appears before that text could appear, then warn that a
// modifier appears after the type annotation.
// TODO: in the future, account for the lengths of declaration annotations. Length of toString
// of the annotation isn't useful, as it might be different length than original input. Can use
// JCTree.getEndPosition(EndPosTable) and com.sun.tools.javac.tree.EndPosTable, but it requires
// -Xjcov.
AnnotationTree firstAnno = annotations.get(0);
if (!modifierSet.isEmpty() && isTypeAnnotation(firstAnno)) {
int precedingTextLength = 0;
for (Modifier m : modifierSet) {
precedingTextLength += m.toString().length() + 1; // +1 for the space
}
int annoStartPos = ((JCTree) firstAnno).getStartPosition();
int varStartPos = ((JCTree) node).getStartPosition();
if (annoStartPos < varStartPos + precedingTextLength) {
checker.reportWarning(node, "type.anno.before.modifier", firstAnno, modifierSet);
}
}
}
/**
* Return true if the given annotation is a type annotation: that is, its definition is
* meta-annotated with {@code @Target({TYPE_USE,....})}.
*/
private boolean isTypeAnnotation(AnnotationTree anno) {
Tree annoType = anno.getAnnotationType();
ClassSymbol annoSymbol;
switch (annoType.getKind()) {
case IDENTIFIER:
annoSymbol = (ClassSymbol) ((JCIdent) annoType).sym;
break;
case MEMBER_SELECT:
annoSymbol = (ClassSymbol) ((JCFieldAccess) annoType).sym;
break;
default:
throw new Error("Unhandled kind: " + annoType.getKind() + " for " + anno);
}
for (AnnotationMirror metaAnno : annoSymbol.getAnnotationMirrors()) {
if (AnnotationUtils.areSameByName(metaAnno, TARGET)) {
AnnotationValue av = metaAnno.getElementValues().get(targetValueElement);
return AnnotationUtils.annotationValueContainsToString(av, "TYPE_USE");
}
}
return false;
}
/**
* Performs two checks: subtyping and assignability checks, using {@link
* #commonAssignmentCheck(Tree, ExpressionTree, String, Object[])}.
*
* <p>If the subtype check fails, it issues a "assignment" error.
*/
@Override
public Void visitAssignment(AssignmentTree node, Void p) {
Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
visitorState.setAssignmentContext(
Pair.of((Tree) node.getVariable(), atypeFactory.getAnnotatedType(node.getVariable())));
try {
commonAssignmentCheck(node.getVariable(), node.getExpression(), "assignment");
return super.visitAssignment(node, p);
} finally {
visitorState.setAssignmentContext(preAssignmentContext);
}
}
/**
* 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" error.
*/
@Override
public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void p) {
AnnotatedTypeMirror var = atypeFactory.getAnnotatedTypeLhs(node.getVariable());
AnnotatedTypeMirror iteratedType = atypeFactory.getIterableElementType(node.getExpression());
boolean valid = validateTypeOf(node.getVariable());
if (valid) {
commonAssignmentCheck(var, iteratedType, node.getExpression(), "enhancedfor");
}
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);
}
ParameterizedExecutableType mType = atypeFactory.methodFromUse(node);
AnnotatedExecutableType invokedMethod = mType.executableType;
List<AnnotatedTypeMirror> typeargs = mType.typeArgs;
if (!atypeFactory.ignoreUninferredTypeArguments) {
for (AnnotatedTypeMirror typearg : typeargs) {
if (typearg.getKind() == TypeKind.WILDCARD
&& ((AnnotatedWildcardType) typearg).isUninferredTypeArgument()) {
checker.reportError(
node, "type.arguments.not.inferred", invokedMethod.getElement().getSimpleName());
break; // only issue error once per method
}
}
}
List<AnnotatedTypeParameterBounds> paramBounds =
CollectionsPlume.mapList(
AnnotatedTypeVariable::getBounds, invokedMethod.getTypeVariables());
ExecutableElement method = invokedMethod.getElement();
CharSequence methodName = ElementUtils.getSimpleNameOrDescription(method);
try {
checkTypeArguments(
node,
paramBounds,
typeargs,
node.getTypeArguments(),
methodName,
invokedMethod.getTypeVariables());
List<AnnotatedTypeMirror> params =
AnnotatedTypes.expandVarArgs(atypeFactory, invokedMethod, node.getArguments());
checkArguments(params, node.getArguments(), methodName, method.getParameters());
checkVarargs(invokedMethod, node);
if (ElementUtils.isMethod(
invokedMethod.getElement(), vectorCopyInto, atypeFactory.getProcessingEnv())) {
typeCheckVectorCopyIntoArgument(node, params);
}
ExecutableElement invokedMethodElement = invokedMethod.getElement();
if (!ElementUtils.isStatic(invokedMethodElement) && !TreeUtils.isSuperConstructorCall(node)) {
checkMethodInvocability(invokedMethod, node);
}
// check precondition annotations
checkPreconditions(
node, atypeFactory.getContractsFromMethod().getPreconditions(invokedMethodElement));
if (TreeUtils.isSuperConstructorCall(node)) {
checkSuperConstructorCall(node);
} else if (TreeUtils.isThisConstructorCall(node)) {
checkThisConstructorCall(node);
}
} catch (RuntimeException t) {
// Sometimes the type arguments are inferred incorrect which causes crashes. Once #979
// is fixed this should be removed and crashes should be reported normally.
if (node.getTypeArguments().size() == typeargs.size()) {
// They type arguments were explicitly written.
throw t;
}
if (!atypeFactory.ignoreUninferredTypeArguments) {
checker.reportError(
node, "type.arguments.not.inferred", invokedMethod.getElement().getSimpleName());
} // else ignore the crash.
}
// 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 the following rule is satisfied: The type on a constructor declaration must be a
* supertype of the return type of "this()" invocation within that constructor.
*
* <p>Subclasses can override this method to change the behavior for just "this" constructor
* class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to
* change the behavior for "this" and "super" constructor calls.
*
* @param thisCall the AST node for the constructor call
*/
protected void checkThisConstructorCall(MethodInvocationTree thisCall) {
checkThisOrSuperConstructorCall(thisCall, "this.invocation");
}
/**
* Checks that the following rule is satisfied: The type on a constructor declaration must be a
* supertype of the return type of "super()" invocation within that constructor.
*
* <p>Subclasses can override this method to change the behavior for just "super" constructor
* class. Or override {@link #checkThisOrSuperConstructorCall(MethodInvocationTree, String)} to
* change the behavior for "this" and "super" constructor calls.
*
* @param superCall the AST node for the super constructor call
*/
protected void checkSuperConstructorCall(MethodInvocationTree superCall) {
checkThisOrSuperConstructorCall(superCall, "super.invocation");
}
/**
* Checks that the following rule is satisfied: The type on a constructor declaration must be a
* supertype of the return type of "this()" or "super()" invocation within that constructor.
*
* @param call the AST node for the constructor call
* @param errorKey the error message key to use if the check fails
*/
protected void checkThisOrSuperConstructorCall(
MethodInvocationTree call, @CompilerMessageKey String errorKey) {
TreePath path = atypeFactory.getPath(call);
MethodTree enclosingMethod = TreePathUtil.enclosingMethod(path);
AnnotatedTypeMirror superType = atypeFactory.getAnnotatedType(call);
AnnotatedExecutableType constructorType = atypeFactory.getAnnotatedType(enclosingMethod);
Set<? extends AnnotationMirror> topAnnotations =
atypeFactory.getQualifierHierarchy().getTopAnnotations();
for (AnnotationMirror topAnno : topAnnotations) {
AnnotationMirror superTypeMirror = superType.getAnnotationInHierarchy(topAnno);
AnnotationMirror constructorTypeMirror =
constructorType.getReturnType().getAnnotationInHierarchy(topAnno);
if (!atypeFactory.getQualifierHierarchy().isSubtype(superTypeMirror, constructorTypeMirror)) {
checker.reportError(call, errorKey, constructorTypeMirror, call, superTypeMirror);
}
}
}
/**
* A helper method to check that the array type of actual varargs is a subtype of the
* corresponding required varargs, and issues "argument" error if it's not a subtype of the
* required one.
*
* <p>Note it's required that type checking for each element in varargs is executed by the caller
* before or after calling this method.
*
* @see #checkArguments
* @param invokedMethod the method type to be invoked
* @param tree method or constructor invocation tree
*/
protected void checkVarargs(AnnotatedExecutableType invokedMethod, Tree tree) {
if (!invokedMethod.isVarArgs()) {
return;
}
List<AnnotatedTypeMirror> formals = invokedMethod.getParameterTypes();
int numFormals = formals.size();
int lastArgIndex = numFormals - 1;
AnnotatedArrayType lastParamAnnotatedType = (AnnotatedArrayType) formals.get(lastArgIndex);
// We will skip type checking so that we avoid duplicating error message if the last argument is
// same depth with the depth of formal varargs because type checking is already done in
// checkArguments.
List<? extends ExpressionTree> args;
switch (tree.getKind()) {
case METHOD_INVOCATION:
args = ((MethodInvocationTree) tree).getArguments();
break;
case NEW_CLASS:
args = ((NewClassTree) tree).getArguments();
break;
default:
throw new BugInCF("Unexpected kind of tree: " + tree);
}
if (numFormals == args.size()) {
AnnotatedTypeMirror lastArgType = atypeFactory.getAnnotatedType(args.get(args.size() - 1));
if (lastArgType.getKind() == TypeKind.ARRAY
&& AnnotatedTypes.getArrayDepth(lastParamAnnotatedType)
== AnnotatedTypes.getArrayDepth((AnnotatedArrayType) lastArgType)) {
return;
}
}
AnnotatedTypeMirror wrappedVarargsType = atypeFactory.getAnnotatedTypeVarargsArray(tree);
// When dataflow analysis is not enabled, it will be null and we can suppose there is no
// annotation to be checked for generated varargs array.
if (wrappedVarargsType == null) {
return;
}
// The component type of wrappedVarargsType might not be a subtype of the component type of
// lastParamAnnotatedType due to the difference of type inference between for an expression
// and an invoked method element. We can consider that the component type of actual is same
// with formal one because type checking for elements will be done in checkArguments. This
// is also needed to avoid duplicating error message caused by elements in varargs.
if (wrappedVarargsType.getKind() == TypeKind.ARRAY) {
((AnnotatedArrayType) wrappedVarargsType)
.setComponentType(lastParamAnnotatedType.getComponentType());
}
commonAssignmentCheck(lastParamAnnotatedType, wrappedVarargsType, tree, "varargs");
}
/**
* Checks that all the given {@code preconditions} hold true immediately prior to the method
* invocation or variable access at {@code tree}.
*
* @param tree the method invocation; immediately prior to it, 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;
}
StringToJavaExpression stringToJavaExpr =
stringExpr -> StringToJavaExpression.atMethodInvocation(stringExpr, tree, checker);
for (Contract c : preconditions) {
Precondition p = (Precondition) c;
String expressionString = p.expressionString;
AnnotationMirror anno =
c.viewpointAdaptDependentTypeAnnotation(atypeFactory, stringToJavaExpr, tree);
JavaExpression exprJe;
try {
exprJe = StringToJavaExpression.atMethodInvocation(expressionString, tree, checker);
} catch (JavaExpressionParseException e) {
// report errors here
checker.report(tree, e.getDiagMessage());
return;
}
CFAbstractStore<?, ?> store = atypeFactory.getStoreBefore(tree);
CFAbstractValue<?> value = null;
if (CFAbstractStore.canInsertJavaExpression(exprJe)) {
value = store.getValue(exprJe);
}
AnnotationMirror inferredAnno = null;
if (value != null) {
QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
Set<AnnotationMirror> annos = value.getAnnotations();
inferredAnno = hierarchy.findAnnotationInSameHierarchy(annos, anno);
}
if (!checkContract(exprJe, anno, inferredAnno, store)) {
if (exprJe != null) {
expressionString = exprJe.toString();
}
checker.reportError(
tree,
"contracts.precondition",
tree.getMethodSelect().toString(),
contractExpressionAndType(expressionString, inferredAnno),
contractExpressionAndType(expressionString, anno));
}
}
}
/**
* 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(
JavaExpression expr,
AnnotationMirror necessaryAnnotation,
AnnotationMirror inferredAnnotation,
CFAbstractStore<?, ?> store) {
return inferredAnnotation != null
&& atypeFactory.getQualifierHierarchy().isSubtype(inferredAnnotation, necessaryAnnotation);
}
/**
* Type checks the method arguments of {@code Vector.copyInto()}.
*
* <p>The Checker Framework special-cases the method invocation, as its type safety cannot be
* expressed by Java's type system.
*
* <p>For a Vector {@code v} of type {@code Vector<E>}, the method invocation {@code
* v.copyInto(arr)} is type-safe iff {@code arr} is an 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;
}
AnnotatedTypeMirror argComponent = passedAsArray.getComponentType();
AnnotatedTypeMirror vectorTypeArg = receiverAsVector.getTypeArguments().get(0);
Tree errorLocation = node.getArguments().get(0);
if (TypesUtils.isErasedSubtype(
vectorTypeArg.getUnderlyingType(), argComponent.getUnderlyingType(), types)) {
commonAssignmentCheck(argComponent, vectorTypeArg, errorLocation, "vector.copyinto");
} else {
checker.reportError(errorLocation, "vector.copyinto", vectorTypeArg, argComponent);
}
}
/**
* 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(TreeUtils.constructor(node))) {
return super.visitNewClass(node, p);
}
ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(node);
AnnotatedExecutableType constructorType = fromUse.executableType;
List<AnnotatedTypeMirror> typeargs = fromUse.typeArgs;
List<? extends ExpressionTree> passedArguments = node.getArguments();
List<AnnotatedTypeMirror> params =
AnnotatedTypes.expandVarArgs(atypeFactory, constructorType, passedArguments);
ExecutableElement constructor = constructorType.getElement();
CharSequence constructorName = ElementUtils.getSimpleNameOrDescription(constructor);
checkArguments(params, passedArguments, constructorName, constructor.getParameters());
checkVarargs(constructorType, node);
List<AnnotatedTypeParameterBounds> paramBounds =
CollectionsPlume.mapList(
AnnotatedTypeVariable::getBounds, constructorType.getTypeVariables());
checkTypeArguments(
node,
paramBounds,
typeargs,
node.getTypeArguments(),
constructorName,
constructor.getTypeParameters());
boolean valid = validateTypeOf(node);
if (valid) {
AnnotatedDeclaredType dt = atypeFactory.getAnnotatedType(node);
atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(dt, node);
checkConstructorInvocation(dt, constructorType, node);
}
// Do not call super, as that would observe the arguments without
// a set assignment context.
scan(node.getEnclosingExpression(), p);
scan(node.getIdentifier(), p);
scan(node.getClassBody(), p);
return null;
}
@Override
public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
AnnotatedExecutableType functionType = atypeFactory.getFunctionTypeFromTree(node);
if (node.getBody().getKind() != Tree.Kind.BLOCK) {
// Check return type for single statement returns here.
AnnotatedTypeMirror ret = functionType.getReturnType();
if (ret.getKind() != TypeKind.VOID) {
visitorState.setAssignmentContext(Pair.of((Tree) node, ret));
commonAssignmentCheck(ret, (ExpressionTree) node.getBody(), "return");
}
}
// Check parameters
for (int i = 0; i < functionType.getParameterTypes().size(); ++i) {
AnnotatedTypeMirror lambdaParameter =
atypeFactory.getAnnotatedType(node.getParameters().get(i));
commonAssignmentCheck(
lambdaParameter,
functionType.getParameterTypes().get(i),
node.getParameters().get(i),
"lambda.param",
i);
}
// TODO: Postconditions?
// https://github.com/typetools/checker-framework/issues/801
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" 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> preAssignmentContext = visitorState.getAssignmentContext();
try {
Tree enclosing =
TreePathUtil.enclosingOfKind(
getCurrentPath(),
new HashSet<>(Arrays.asList(Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION)));
AnnotatedTypeMirror ret = null;
if (enclosing.getKind() == Tree.Kind.METHOD) {
MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath());
boolean valid = validateTypeOf(enclosing);
if (valid) {
ret = atypeFactory.getMethodReturnType(enclosingMethod, node);
}
} else {
AnnotatedExecutableType result =
atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing);
ret = result.getReturnType();
}
if (ret != null) {
visitorState.setAssignmentContext(Pair.of((Tree) node, ret));
commonAssignmentCheck(ret, node.getExpression(), "return");
}
return super.visitReturn(node, p);
} finally {
visitorState.setAssignmentContext(preAssignmentContext);
}
}
/**
* 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;
}
TypeElement anno = (TypeElement) TreeInfo.symbol((JCTree) node.getAnnotationType());
Name annoName = anno.getQualifiedName();
if (annoName.contentEquals(DefaultQualifier.class.getName())
|| annoName.contentEquals(SuppressWarnings.class.getName())) {
// Skip these two annotations, as we don't care about the arguments to them.
return null;
}
List<ExecutableElement> methods = ElementFilter.methodsIn(anno.getEnclosedElements());
// Mapping from argument simple name to its annotated type.
Map<String, AnnotatedTypeMirror> annoTypes = new HashMap<>(methods.size());
for (ExecutableElement meth : methods) {
AnnotatedExecutableType exeatm = atypeFactory.getAnnotatedType(meth);
AnnotatedTypeMirror retty = exeatm.getReturnType();
annoTypes.put(meth.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> preAssignmentContext = visitorState.getAssignmentContext();
{
// Determine and set the new assignment context.
ExpressionTree var = at.getVariable();
assert var instanceof IdentifierTree : "Expected IdentifierTree as context. Found: " + var;
AnnotatedTypeMirror meth = atypeFactory.getAnnotatedType(var);
assert meth instanceof AnnotatedExecutableType
: "Expected AnnotatedExecutableType as context. Found: " + meth;
AnnotatedTypeMirror newctx = ((AnnotatedExecutableType) meth).getReturnType();
visitorState.setAssignmentContext(Pair.of((Tree) null, newctx));
}
try {
AnnotatedTypeMirror actual = atypeFactory.getAnnotatedType(at.getExpression());
if (expected.getKind() != TypeKind.ARRAY) {
// Expected is not an array -> direct comparison.
commonAssignmentCheck(expected, actual, at.getExpression(), "annotation");
} else {
if (actual.getKind() == TypeKind.ARRAY) {
// Both actual and expected are arrays.
commonAssignmentCheck(expected, actual, at.getExpression(), "annotation");
} else {
// The declaration is an array type, but just a single
// element is given.
commonAssignmentCheck(
((AnnotatedArrayType) expected).getComponentType(),
actual,
at.getExpression(),
"annotation");
}
}
} finally {
visitorState.setAssignmentContext(preAssignmentContext);
}
}
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");
this.commonAssignmentCheck(cond, node.getFalseExpression(), "conditional");
return super.visitConditionalExpression(node, p);
}
// **********************************************************************
// Check for illegal re-assignment
// **********************************************************************
/** Performs assignability check. */
@Override
public Void visitUnary(UnaryTree node, Void p) {
Tree.Kind nodeKind = node.getKind();
if ((nodeKind == Tree.Kind.PREFIX_DECREMENT)
|| (nodeKind == Tree.Kind.PREFIX_INCREMENT)
|| (nodeKind == Tree.Kind.POSTFIX_DECREMENT)
|| (nodeKind == Tree.Kind.POSTFIX_INCREMENT)) {
AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(node.getExpression());
AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedTypeRhsUnaryAssign(node);
String errorKey =
(nodeKind == Tree.Kind.PREFIX_INCREMENT || nodeKind == Tree.Kind.POSTFIX_INCREMENT)
? "unary.increment"
: "unary.decrement";
commonAssignmentCheck(varType, valueType, node, errorKey);
}
return super.visitUnary(node, p);
}
/** Performs assignability check. */
@Override
public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
// If node is the tree representing 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");
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);
atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(arrayType, node);
if (node.getInitializers() != null) {
checkArrayInitialization(arrayType.getComponentType(), node.getInitializers());
}
}
return super.visitNewArray(node, p);
}
/**
* If the lint option "cast:redundant" is set, this methods issues a warning if the cast is
* redundant.
*/
protected void checkTypecastRedundancy(TypeCastTree typeCastTree) {
if (!checker.getLintOption("cast:redundant", false)) {
return;
}
AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree);
AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression());
if (castType.equals(exprType)) {
checker.reportWarning(typeCastTree, "cast.redundant", castType);
}
}
/**
* Issues a warning if the given explicitly-written typecast is unsafe. Does nothing if the lint
* option "cast:unsafe" is not set. Only primary qualifiers are checked unless the command line
* option "checkCastElementType" is supplied.
*
* @param typeCastTree an explicitly-written typecast
*/
protected void checkTypecastSafety(TypeCastTree typeCastTree) {
if (!checker.getLintOption("cast:unsafe", true)) {
return;
}
AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(typeCastTree);
AnnotatedTypeMirror exprType = atypeFactory.getAnnotatedType(typeCastTree.getExpression());
boolean calledOnce = false;
for (AnnotationMirror top : atypeFactory.getQualifierParameterHierarchies(castType)) {
if (!isInvariantTypeCastSafe(castType, exprType, top)) {
checker.reportError(
typeCastTree,
"invariant.cast.unsafe",
exprType.toString(true),
castType.toString(true));
}
calledOnce = true; // don't issue cast unsafe warning.
}
// We cannot do a simple test of casting, as isSubtypeOf requires
// the input types to be subtypes according to Java.
if (!calledOnce && !isTypeCastSafe(castType, exprType)) {
checker.reportWarning(
typeCastTree, "cast.unsafe", exprType.toString(true), castType.toString(true));
}
}
/**
* Returns true if the cast is safe.
*
* <p>Only primary qualifiers are checked unless the command line option "checkCastElementType" is
* supplied.
*
* @param castType annotated type of the cast
* @param exprType annotated type of the casted expression
* @return true if the type cast is safe, false otherwise
*/
protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) {
final TypeKind castTypeKind = castType.getKind();
if (castTypeKind == TypeKind.DECLARED) {
// Don't issue an error if the annotations are equivalent to the qualifier upper bound of the
// type.
AnnotatedDeclaredType castDeclared = (AnnotatedDeclaredType) castType;
Set<AnnotationMirror> bounds =
atypeFactory.getTypeDeclarationBounds(castDeclared.getUnderlyingType());
if (AnnotationUtils.areSame(castDeclared.getAnnotations(), bounds)) {
return true;
}
}
QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
Set<AnnotationMirror> castAnnos;
if (!checker.hasOption("checkCastElementType")) {
// checkCastElementType option wasn't specified, so only check effective annotations.
castAnnos = castType.getEffectiveAnnotations();
} else {
AnnotatedTypeMirror newCastType;
if (castTypeKind == TypeKind.TYPEVAR) {
newCastType = ((AnnotatedTypeVariable) castType).getUpperBound();
} else {
newCastType = castType;
}
AnnotatedTypeMirror newExprType;
if (exprType.getKind() == TypeKind.TYPEVAR) {
newExprType = ((AnnotatedTypeVariable) exprType).getUpperBound();
} else {
newExprType = exprType;
}
if (!atypeFactory.getTypeHierarchy().isSubtype(newExprType, newCastType)) {
return false;
}
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
return 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.
return false;
}
} else if (castTypeKind == TypeKind.TYPEVAR && exprType.getKind() == TypeKind.TYPEVAR) {
// If both the cast type and the casted expression are type variables, then check the
// bounds.
Set<AnnotationMirror> lowerBoundAnnotationsCast =
AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualifierHierarchy, castType);
Set<AnnotationMirror> lowerBoundAnnotationsExpr =
AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualifierHierarchy, exprType);
return qualifierHierarchy.isSubtype(lowerBoundAnnotationsExpr, lowerBoundAnnotationsCast)
&& qualifierHierarchy.isSubtype(
exprType.getEffectiveAnnotations(), castType.getEffectiveAnnotations());
}
if (castTypeKind == TypeKind.TYPEVAR) {
// If the cast type is a type var, but the expression is not, then check that the
// type of the expression is a subtype of the lower bound.
castAnnos = AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualifierHierarchy, castType);
} else {
castAnnos = castType.getAnnotations();
}
}
AnnotatedTypeMirror exprTypeWidened = atypeFactory.getWidenedType(exprType, castType);
return qualifierHierarchy.isSubtype(exprTypeWidened.getEffectiveAnnotations(), castAnnos);
}
/**
* Return whether or not casting the exprType to castType is legal.
*
* @param castType an invariant type
* @param exprType type of the expressions that is cast which may or may not be invariant
* @param top the top qualifier of the hierarchy to check
* @return whether or not casting the exprType to castType is legal
*/
private boolean isInvariantTypeCastSafe(
AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType, AnnotationMirror top) {
if (!isTypeCastSafe(castType, exprType)) {
return false;
}
AnnotationMirror castTypeAnno = castType.getEffectiveAnnotationInHierarchy(top);
AnnotationMirror exprTypeAnno = exprType.getEffectiveAnnotationInHierarchy(top);
if (atypeFactory.hasQualifierParameterInHierarchy(exprType, top)) {
// The isTypeCastSafe call above checked that the exprType is a subtype of castType,
// so just check the reverse to check that the qualifiers are equivalent.
return atypeFactory.getQualifierHierarchy().isSubtype(castTypeAnno, exprTypeAnno);
}
// Otherwise the cast is unsafe, unless the qualifiers on both cast and expr are bottom.
AnnotationMirror bottom = atypeFactory.getQualifierHierarchy().getBottomAnnotation(top);
return AnnotationUtils.areSame(castTypeAnno, bottom)
&& AnnotationUtils.areSame(exprTypeAnno, bottom);
}
@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);
checkTypecastRedundancy(node);
}
if (atypeFactory.getDependentTypesHelper().hasDependentAnnotations()) {
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node);
atypeFactory.getDependentTypesHelper().checkTypeForErrorExpressions(type, node.getType());
}
if (node.getType().getKind() == Tree.Kind.INTERSECTION_TYPE) {
AnnotatedIntersectionType intersection =
(AnnotatedIntersectionType) atypeFactory.getAnnotatedType(node);
checkExplicitAnnotationsOnIntersectionBounds(
intersection, ((IntersectionTypeTree) node.getType()).getBounds());
}
return super.visitTypeCast(node, p);
// return scan(node.getExpression(), p);
}
@Override
public Void visitInstanceOf(InstanceOfTree node, Void p) {
// The "reference type" is the type after "instanceof".
Tree refTypeTree = node.getType();
validateTypeOf(refTypeTree);
if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) {
AnnotatedTypeMirror refType = atypeFactory.getAnnotatedType(refTypeTree);
AnnotatedTypeMirror expType = atypeFactory.getAnnotatedType(node.getExpression());
if (!refType.hasAnnotation(NonNull.class)
&& atypeFactory.getTypeHierarchy().isSubtype(refType, expType)
&& !refType.getAnnotations().equals(expType.getAnnotations())) {
checker.reportWarning(node, "instanceof.unsafe", expType, refType);
}
}
return super.visitInstanceOf(node, p);
}
@Override
public Void visitArrayAccess(ArrayAccessTree node, Void p) {
Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
try {
visitorState.setAssignmentContext(null);
scan(node.getExpression(), p);
scan(node.getIndex(), p);
} finally {
visitorState.setAssignmentContext(preAssignmentContext);
}
return null;
}
/**
* Checks the type of the exception parameter. Subclasses should override {@link
* #checkExceptionParameter} 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);
}
/**
* Rather than overriding this method, clients should often override {@link
* #visitAnnotatedType(List,Tree)}. That method also handles the case of annotations at the
* beginning of a variable or method declaration. javac parses all those annotations as being on
* the variable or method declaration, even though the ones that are type annotations logically
* belong to the variable type or method return type.
*/
@Override
public Void visitAnnotatedType(AnnotatedTypeTree node, Void p) {
visitAnnotatedType(null, node);
return super.visitAnnotatedType(node, p);
}
/**
* Checks an annotated type. Invoked by {@link #visitAnnotatedType(AnnotatedTypeTree, Void)},
* {@link #visitVariable}, and {@link #visitMethod}. Exists to prevent code duplication among the
* three. Checking in {@code visitVariable} and {@code visitMethod} is needed because there isn't
* an AnnotatedTypeTree within a variable declaration or for a method return type -- all the
* annotations are attached to the VariableTree or MethodTree, respectively.
*
* @param annoTrees annotations written before a variable/method declaration, if this type is from
* one; null otherwise. This might contain type annotations that the Java parser attached to
* the declaration rather than to the type.
* @param typeTree the type that any type annotations in annoTrees apply to
*/
public void visitAnnotatedType(
@Nullable List<? extends AnnotationTree> annoTrees, Tree typeTree) {
warnAboutIrrelevantJavaTypes(annoTrees, typeTree);
}
/**
* Warns if a type annotation is written on a Java type that is not listed in
* the @RelevantJavaTypes annotation.
*
* @param annoTrees annotations written before a variable/method declaration, if this type is from
* one; null otherwise. This might contain type annotations that the Java parser attached to
* the declaration rather than to the type.
* @param typeTree the type that any type annotations in annoTrees apply to
*/
public void warnAboutIrrelevantJavaTypes(
@Nullable List<? extends AnnotationTree> annoTrees, Tree typeTree) {
if (atypeFactory.relevantJavaTypes == null) {
return;
}
Tree t = typeTree;
while (true) {
switch (t.getKind()) {
// Recurse for compound types whose top level is not at the far left.
case ARRAY_TYPE:
t = ((ArrayTypeTree) t).getType();
continue;
case MEMBER_SELECT:
t = ((MemberSelectTree) t).getExpression();
continue;
case PARAMETERIZED_TYPE:
t = ((ParameterizedTypeTree) t).getType();
continue;
// Base cases
case PRIMITIVE_TYPE:
case IDENTIFIER:
List<AnnotationTree> supportedAnnoTrees = supportedAnnoTrees(annoTrees);
if (!supportedAnnoTrees.isEmpty() && !atypeFactory.isRelevant(TreeUtils.typeOf(t))) {
checker.reportError(t, "anno.on.irrelevant", supportedAnnoTrees, t);
}
return;
case ANNOTATED_TYPE:
AnnotatedTypeTree at = (AnnotatedTypeTree) t;
ExpressionTree underlying = at.getUnderlyingType();
List<AnnotationTree> annos = supportedAnnoTrees(at.getAnnotations());
if (!annos.isEmpty() && !atypeFactory.isRelevant(TreeUtils.typeOf(underlying))) {
checker.reportError(t, "anno.on.irrelevant", annos, underlying);
}
return;
default:
return;
}
}
}
/**
* Returns a new list containing only the supported annotations from its argument -- that is,
* those that are part of the current type system.
*
* <p>This method ignores aliases of supported annotations that are declaration annotations,
* because they may apply to inner types.
*
* @param annoTrees annotation trees
* @return a new list containing only the supported annotations from its argument
*/
private List<AnnotationTree> supportedAnnoTrees(List<? extends AnnotationTree> annoTrees) {
List<AnnotationTree> result = new ArrayList<>(1);
for (AnnotationTree at : annoTrees) {
AnnotationMirror anno = TreeUtils.annotationFromAnnotationTree(at);
if (!AnnotationUtils.isDeclarationAnnotation(anno)
&& atypeFactory.isSupportedQualifier(anno)) {
result.add(at);
}
}
return result;
}
// **********************************************************************
// Helper methods to provide a single overriding point
// **********************************************************************
/** Cache to avoid calling {@link #getExceptionParameterLowerBoundAnnotations} more than once. */
private Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotationsCache = null;
/** The same as {@link #getExceptionParameterLowerBoundAnnotations}, but uses a cache. */
private Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotationsCached() {
if (getExceptionParameterLowerBoundAnnotationsCache == null) {
getExceptionParameterLowerBoundAnnotationsCache =
getExceptionParameterLowerBoundAnnotations();
}
return getExceptionParameterLowerBoundAnnotationsCache;
}
/**
* 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 =
getExceptionParameterLowerBoundAnnotationsCached();
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.reportError(node.getParameter(), "exception.parameter", found, required);
}
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.reportError(node.getParameter(), "exception.parameter", foundAltern, required);
}
}
}
}
}
/**
* Returns a set of AnnotationMirrors that is a lower bound for exception parameters.
*
* <p>This implementation returns top; subclasses can change this behavior.
*
* <p>Note: by default this method is called by {@link #getThrowUpperBoundAnnotations()}, so that
* this annotation is enforced.
*
* @return set of annotation mirrors, one per hierarchy, that form 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.reportError(node.getExpression(), "throw", found, required);
}
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.reportError(node.getExpression(), "throw", foundEffective, required);
}
break;
case UNION:
AnnotatedUnionType unionType = (AnnotatedUnionType) throwType;
Set<AnnotationMirror> foundPrimary = unionType.getAnnotations();
if (!atypeFactory.getQualifierHierarchy().isSubtype(foundPrimary, required)) {
checker.reportError(node.getExpression(), "throw", foundPrimary, required);
}
for (AnnotatedTypeMirror altern : unionType.getAlternatives()) {
if (!atypeFactory.getQualifierHierarchy().isSubtype(altern.getAnnotations(), required)) {
checker.reportError(node.getExpression(), "throw", altern.getAnnotations(), required);
}
}
break;
default:
throw new BugInCF("Unexpected throw expression type: " + throwType.getKind());
}
}
/**
* 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 key to use if the check fails
* @param extraArgs arguments to the error message key, before "found" and "expected" types
*/
protected void commonAssignmentCheck(
Tree varTree,
ExpressionTree valueExp,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
AnnotatedTypeMirror varType = atypeFactory.getAnnotatedTypeLhs(varTree);
assert varType != null : "no variable found for tree: " + varTree;
if (!validateType(varTree, varType)) {
return;
}
commonAssignmentCheck(varType, valueExp, errorKey, extraArgs);
}
/**
* 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 for the lvalue (usually a variable)
* @param valueExp the AST node for the rvalue (the new value)
* @param errorKey the error message key to use if the check fails
* @param extraArgs arguments to the error message key, before "found" and "expected" types
*/
protected void commonAssignmentCheck(
AnnotatedTypeMirror varType,
ExpressionTree valueExp,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
if (shouldSkipUses(valueExp)) {
return;
}
if (valueExp.getKind() == Tree.Kind.MEMBER_REFERENCE
|| valueExp.getKind() == Tree.Kind.LAMBDA_EXPRESSION) {
// Member references and lambda expressions are type checked separately
// and do not need to be checked again as arguments.
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, extraArgs);
}
/**
* 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 key to use if the check fails
* @param extraArgs arguments to the error message key, before "found" and "expected" types
*/
protected void commonAssignmentCheck(
AnnotatedTypeMirror varType,
AnnotatedTypeMirror valueType,
Tree valueTree,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
commonAssignmentCheckStartDiagnostic(varType, valueType, valueTree);
AnnotatedTypeMirror widenedValueType = atypeFactory.getWidenedType(valueType, varType);
boolean success = atypeFactory.getTypeHierarchy().isSubtype(widenedValueType, varType);
// TODO: integrate with subtype test.
if (success) {
for (Class<? extends Annotation> mono : atypeFactory.getSupportedMonotonicTypeQualifiers()) {
if (valueType.hasAnnotation(mono) && varType.hasAnnotation(mono)) {
checker.reportError(
valueTree,
"monotonic",
mono.getSimpleName(),
mono.getSimpleName(),
valueType.toString());
return;
}
}
}
commonAssignmentCheckEndDiagnostic(success, null, varType, valueType, valueTree);
// Use an error key only if it's overridden by a checker.
if (!success) {
FoundRequired pair = FoundRequired.of(valueType, varType);
String valueTypeString = pair.found;
String varTypeString = pair.required;
checker.reportError(
valueTree, errorKey, ArraysPlume.concatenate(extraArgs, valueTypeString, varTypeString));
}
}
/**
* Prints a diagnostic about entering commonAssignmentCheck, if the showchecks option was set.
*
* @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
*/
protected final void commonAssignmentCheckStartDiagnostic(
AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree) {
if (showchecks) {
long valuePos = positions.getStartPosition(root, valueTree);
System.out.printf(
"%s %s (line %3d): actual tree = %s %s%n actual: %s %s%n expected: %s %s%n",
this.getClass().getSimpleName(),
"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());
}
}
/**
* Prints a diagnostic about exiting commonAssignmentCheck, if the showchecks option was set.
*
* @param success whether the check succeeded or failed
* @param extraMessage information about why the result is what it is; may be null
* @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
*/
protected final void commonAssignmentCheckEndDiagnostic(
boolean success,
String extraMessage,
AnnotatedTypeMirror varType,
AnnotatedTypeMirror valueType,
Tree valueTree) {
if (showchecks) {
commonAssignmentCheckEndDiagnostic(
(success
? "success: actual is subtype of expected"
: "FAILURE: actual is not subtype of expected")
+ (extraMessage == null ? "" : " because " + extraMessage),
varType,
valueType,
valueTree);
}
}
/**
* Prints a diagnostic about exiting commonAssignmentCheck, if the showchecks option was set.
*
* <p>Most clients should call {@link #commonAssignmentCheckEndDiagnostic(boolean, String,
* AnnotatedTypeMirror, AnnotatedTypeMirror, Tree)}. The purpose of this method is to permit
* customizing the message that is printed.
*
* @param message the result, plus information about why the result is what it is; may be null
* @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
*/
protected final void commonAssignmentCheckEndDiagnostic(
String message, AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree) {
if (showchecks) {
long valuePos = positions.getStartPosition(root, valueTree);
System.out.printf(
" %s (line %3d): actual tree = %s %s%n actual: %s %s%n expected: %s %s%n",
message,
(root.getLineMap() != null ? root.getLineMap().getLineNumber(valuePos) : -1),
valueTree.getKind(),
valueTree,
valueType.getKind(),
valueType.toString(),
varType.getKind(),
varType.toString());
}
}
/**
* Class that creates string representations of {@link AnnotatedTypeMirror}s which are only
* verbose if required to differentiate the two types.
*/
private static class FoundRequired {
public final String found;
public final String required;
private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeMirror required) {
if (shouldPrintVerbose(found, required)) {
this.found = found.toString(true);
this.required = required.toString(true);
} else {
this.found = found.toString();
this.required = required.toString();
}
}
/** Create a FoundRequired for a type and bounds. */
private FoundRequired(AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) {
if (shouldPrintVerbose(found, required)) {
this.found = found.toString(true);
this.required = required.toString(true);
} else {
this.found = found.toString();
this.required = required.toString();
}
}
/**
* Creates string representations of {@link AnnotatedTypeMirror}s which are only verbose if
* required to differentiate the two types.
*/
static FoundRequired of(AnnotatedTypeMirror found, AnnotatedTypeMirror required) {
return new FoundRequired(found, required);
}
/**
* Creates string representations of {@link AnnotatedTypeMirror} and {@link
* AnnotatedTypeParameterBounds}s which are only verbose if required to differentiate the two
* types.
*/
static FoundRequired of(AnnotatedTypeMirror found, AnnotatedTypeParameterBounds required) {
return new FoundRequired(found, required);
}
}
/**
* 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 neither argumentc contains "@", or there are two annotated types (in either
* ATM) such that their toStrings are the same but their verbose toStrings differ
*/
private static boolean shouldPrintVerbose(AnnotatedTypeMirror atm1, AnnotatedTypeMirror atm2) {
if (!atm1.toString().contains("@") && !atm2.toString().contains("@")) {
return true;
}
return containsSameToString(atm1, atm2);
}
/**
* Return whether or not the verbose toString should be used when printing the annotated type and
* the bounds it is not within.
*
* @param atm the type
* @param bounds the bounds
* @return true iff bounds does not contain "@", or there are two annotated types (in either
* argument) such that their toStrings are the same but their verbose toStrings differ
*/
private static boolean shouldPrintVerbose(
AnnotatedTypeMirror atm, AnnotatedTypeParameterBounds bounds) {
if (!atm.toString().contains("@") && !bounds.toString().contains("@")) {
return true;
}
return containsSameToString(atm, bounds.getUpperBound(), bounds.getLowerBound());
}
/**
* A scanner that indicates whether any (sub-)types have the same toString but different verbose
* toString. If so, the Checker Framework prints types verbosely.
*/
private static SimpleAnnotatedTypeScanner<Boolean, Map<String, String>>
checkContainsSameToString =
new SimpleAnnotatedTypeScanner<>(
(AnnotatedTypeMirror type, Map<String, String> map) -> {
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::logicalOr,
false);
/**
* Return true iff there are two annotated types (anywhere in any ATM) such that their toStrings
* are the same but their verbose toStrings differ. If so, the Checker Framework prints types
* verbosely.
*
* @param atms annotated type mirrors to compare
* @return true iff there are two annotated types (anywhere in any ATM) such that their toStrings
* are the same but their verbose toStrings differ
*/
private static boolean containsSameToString(AnnotatedTypeMirror... atms) {
Map<String, String> simpleToVerbose = new HashMap<>();
for (AnnotatedTypeMirror atm : atms) {
if (checkContainsSameToString.visit(atm, simpleToVerbose)) {
return true;
}
}
return false;
}
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");
}
}
/**
* 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" 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
*/
protected void checkTypeArguments(
Tree toptree,
List<? extends AnnotatedTypeParameterBounds> paramBounds,
List<? extends AnnotatedTypeMirror> typeargs,
List<? extends Tree> typeargTrees,
CharSequence typeOrMethodName,
List<?> paramNames) {
// 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;
}
int size = paramBounds.size();
assert size == typeargs.size()
: "BaseTypeVisitor.checkTypeArguments: mismatch between type arguments: "
+ typeargs
+ " and type parameter bounds"
+ paramBounds;
for (int i = 0; i < size; i++) {
AnnotatedTypeParameterBounds bounds = paramBounds.get(i);
AnnotatedTypeMirror typeArg = typeargs.get(i);
if (isIgnoredUninferredWildcard(bounds.getUpperBound())
|| isIgnoredUninferredWildcard(typeArg)) {
continue;
}
if (shouldBeCaptureConverted(typeArg, bounds)) {
continue;
}
AnnotatedTypeMirror paramUpperBound = bounds.getUpperBound();
if (typeArg.getKind() == TypeKind.WILDCARD) {
paramUpperBound =
atypeFactory.widenToUpperBound(paramUpperBound, (AnnotatedWildcardType) typeArg);
}
Tree reportErrorToTree;
if (typeargTrees == null || typeargTrees.isEmpty()) {
// The type arguments were inferred, report the error on the method invocation.
reportErrorToTree = toptree;
} else {
reportErrorToTree = typeargTrees.get(i);
}
checkHasQualifierParameterAsTypeArgument(typeArg, paramUpperBound, toptree);
commonAssignmentCheck(
paramUpperBound,
typeArg,
reportErrorToTree,
"type.argument",
paramNames.get(i),
typeOrMethodName);
if (!atypeFactory.getTypeHierarchy().isSubtype(bounds.getLowerBound(), typeArg)) {
FoundRequired fr = FoundRequired.of(typeArg, bounds);
checker.reportError(
reportErrorToTree,
"type.argument",
paramNames.get(i),
typeOrMethodName,
fr.found,
fr.required);
}
}
}
/**
* Reports an error if the type argument has a qualifier parameter and the type parameter upper
* bound does not have a qualifier parameter.
*
* @param typeArgument type argument
* @param typeParameterUpperBound upper bound of the type parameter
* @param reportError where to report the error
*/
private void checkHasQualifierParameterAsTypeArgument(
AnnotatedTypeMirror typeArgument,
AnnotatedTypeMirror typeParameterUpperBound,
Tree reportError) {
for (AnnotationMirror top : atypeFactory.getQualifierHierarchy().getTopAnnotations()) {
if (atypeFactory.hasQualifierParameterInHierarchy(typeArgument, top)
&& !getTypeFactory().hasQualifierParameterInHierarchy(typeParameterUpperBound, top)) {
checker.reportError(reportError, "type.argument.hasqualparam", top);
}
}
}
private boolean isIgnoredUninferredWildcard(AnnotatedTypeMirror type) {
return atypeFactory.ignoreUninferredTypeArguments
&& type.getKind() == TypeKind.WILDCARD
&& ((AnnotatedWildcardType) type).isUninferredTypeArgument();
}
// 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;
}
/**
* 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" 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)) {
commonAssignmentCheckStartDiagnostic(methodReceiver, treeReceiver, node);
boolean success = atypeFactory.getTypeHierarchy().isSubtype(treeReceiver, methodReceiver);
commonAssignmentCheckEndDiagnostic(success, null, methodReceiver, treeReceiver, node);
if (!success) {
reportMethodInvocabilityError(node, treeReceiver, methodReceiver);
}
}
}
/**
* Report a method invocability error. Allows checkers to change how the message is output.
*
* @param node the AST node at which to report the error
* @param found the actual type of the receiver
* @param expected the expected type of the receiver
*/
protected void reportMethodInvocabilityError(
MethodInvocationTree node, AnnotatedTypeMirror found, AnnotatedTypeMirror expected) {
checker.reportError(
node,
"method.invocation",
TreeUtils.elementFromUse(node),
found.toString(),
expected.toString());
}
/**
* Check that the (explicit) annotations on a new class tree are comparable to the result type of
* the constructor. Issue an error if not.
*
* <p>Issue a warning if the annotations on the constructor invocation is a subtype of the
* constructor result type. This is equivalent to down-casting.
*/
protected void checkConstructorInvocation(
AnnotatedDeclaredType invocation,
AnnotatedExecutableType constructor,
NewClassTree newClassTree) {
// Only check the primary annotations, the type arguments are checked elsewhere.
Set<AnnotationMirror> explicitAnnos = atypeFactory.fromNewClass(newClassTree).getAnnotations();
if (explicitAnnos.isEmpty()) {
return;
}
Set<AnnotationMirror> resultAnnos = constructor.getReturnType().getAnnotations();
for (AnnotationMirror explicit : explicitAnnos) {
AnnotationMirror resultAnno =
atypeFactory.getQualifierHierarchy().findAnnotationInSameHierarchy(resultAnnos, explicit);
// The return type of the constructor (resultAnnos) must be comparable to the
// annotations on the constructor invocation (explicitAnnos).
if (!(atypeFactory.getQualifierHierarchy().isSubtype(explicit, resultAnno)
|| atypeFactory.getQualifierHierarchy().isSubtype(resultAnno, explicit))) {
checker.reportError(
newClassTree, "constructor.invocation", constructor.toString(), explicit, resultAnno);
return;
} else if (!atypeFactory.getQualifierHierarchy().isSubtype(resultAnno, explicit)) {
// Issue a warning if the annotations on the constructor invocation is a subtype of
// the constructor result type. This is equivalent to down-casting.
checker.reportWarning(
newClassTree, "cast.unsafe.constructor.invocation", resultAnno, explicit);
return;
}
}
// 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" 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.
*
* @see #checkVarargs(AnnotatedTypeMirror.AnnotatedExecutableType, Tree)
* @param requiredArgs the required types. This may differ from the formal parameter types,
* because it replaces a varargs parameter by multiple parameters with the vararg's element
* type.
* @param passedArgs the expressions passed to the corresponding types
* @param executableName the name of the method or constructor being called
* @param paramNames the names of the callee's formal parameters
*/
protected void checkArguments(
List<? extends AnnotatedTypeMirror> requiredArgs,
List<? extends ExpressionTree> passedArgs,
CharSequence executableName,
List<?> paramNames) {
int size = requiredArgs.size();
assert size == passedArgs.size()
: "mismatch between required args ("
+ requiredArgs
+ ") and passed args ("
+ passedArgs
+ ")";
int maxParamNamesIndex = paramNames.size() - 1;
// Rather weak assertion, due to how varargs parameters are treated.
assert size >= maxParamNamesIndex
: String.format(
"mismatched lengths %d %d %d checkArguments(%s, %s, %s, %s)",
size,
passedArgs.size(),
paramNames.size(),
listToString(requiredArgs),
listToString(passedArgs),
executableName,
listToString(paramNames));
Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = visitorState.getAssignmentContext();
try {
for (int i = 0; i < size; ++i) {
visitorState.setAssignmentContext(
Pair.of((Tree) null, (AnnotatedTypeMirror) requiredArgs.get(i)));
commonAssignmentCheck(
requiredArgs.get(i),
passedArgs.get(i),
"argument",
// TODO: for expanded varargs parameters, maybe adjust the name
paramNames.get(Math.min(i, maxParamNamesIndex)),
executableName);
// Also descend into the argument within the correct assignment context.
scan(passedArgs.get(i), null);
}
} finally {
visitorState.setAssignmentContext(preAssignmentContext);
}
}
// com.sun.tools.javac.util.List has a toString that does not include surrounding "[...]",
// making it hard to interpret in messages.
/**
* Produce a printed representation of a list, in the standard format with surrounding "[...]".
*
* @param lst a list to format
* @return the printed representation of the list
*/
private String listToString(List<?> lst) {
StringJoiner result = new StringJoiner(",", "[", "]");
for (Object elt : lst) {
result.add(elt.toString());
}
return result.toString();
}
/**
* Returns true if both types are type variables and outer contains inner. Outer contains inner
* implies: {@literal inner.upperBound <: outer.upperBound outer.lowerBound <: inner.lowerBound}.
*
* @return true if both types are type variables and outer contains inner
*/
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;
}
/**
* Create an OverrideChecker.
*
* <p>This exists so that subclasses can subclass OverrideChecker and use their subclass instead
* of using OverrideChecker itself.
*
* @param overriderTree the AST node of the overriding method or method reference
* @param overriderMethodType the type of the overriding method
* @param overriderType the type enclosing the overrider method, usually an AnnotatedDeclaredType;
* for Method References may be something else
* @param overriderReturnType the return type of the overriding method
* @param overriddenMethodType the type of the overridden method
* @param overriddenType the declared type enclosing the overridden method
* @param overriddenReturnType the return type of the overridden method
* @return an OverrideChecker
*/
protected OverrideChecker createOverrideChecker(
Tree overriderTree,
AnnotatedExecutableType overriderMethodType,
AnnotatedTypeMirror overriderType,
AnnotatedTypeMirror overriderReturnType,
AnnotatedExecutableType overriddenMethodType,
AnnotatedDeclaredType overriddenType,
AnnotatedTypeMirror overriddenReturnType) {
return new OverrideChecker(
overriderTree,
overriderMethodType,
overriderType,
overriderReturnType,
overriddenMethodType,
overriddenType,
overriddenReturnType);
}
/**
* Type checks that a method may override another method. Uses an OverrideChecker subclass as
* created by {@link #createOverrideChecker}. This version of the method uses the annotated type
* factory to get the annotated type of the overriding method, and does NOT expose that type.
*
* @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedExecutableType,
* AnnotatedTypeMirror.AnnotatedDeclaredType, AnnotatedTypeMirror.AnnotatedExecutableType,
* AnnotatedTypeMirror.AnnotatedDeclaredType)
* @param overriderTree declaration tree of overriding method
* @param overriderType type of overriding class
* @param overriddenMethodType type of overridden method
* @param overriddenType type of overridden class
* @return true if the override is allowed
*/
protected boolean checkOverride(
MethodTree overriderTree,
AnnotatedDeclaredType overriderType,
AnnotatedExecutableType overriddenMethodType,
AnnotatedDeclaredType overriddenType) {
// Get the type of the overriding method.
AnnotatedExecutableType overriderMethodType = atypeFactory.getAnnotatedType(overriderTree);
// Call the other version of the method, which takes overriderMethodType. Both versions
// exist to allow checkers to override one or the other depending on their needs.
return checkOverride(
overriderTree, overriderMethodType, overriderType, overriddenMethodType, overriddenType);
}
/**
* Type checks that a method may override another method. Uses an OverrideChecker subclass as
* created by {@link #createOverrideChecker}. This version of the method exposes
* AnnotatedExecutableType of the overriding method. Override this version of the method if you
* need to access that type.
*
* @see #checkOverride(MethodTree, AnnotatedTypeMirror.AnnotatedDeclaredType,
* AnnotatedTypeMirror.AnnotatedExecutableType, AnnotatedTypeMirror.AnnotatedDeclaredType)
* @param overriderTree declaration tree of overriding method
* @param overriderMethodType type of the overriding method
* @param overriderType type of overriding class
* @param overriddenMethodType type of overridden method
* @param overriddenType type of overridden class
* @return true if the override is allowed
*/
protected boolean checkOverride(
MethodTree overriderTree,
AnnotatedExecutableType overriderMethodType,
AnnotatedDeclaredType overriderType,
AnnotatedExecutableType overriddenMethodType,
AnnotatedDeclaredType overriddenType) {
// This needs to be done before overriderMethodType.getReturnType() and
// overriddenMethodType.getReturnType()
if (overriderMethodType.getTypeVariables().isEmpty()
&& !overriddenMethodType.getTypeVariables().isEmpty()) {
overriddenMethodType = overriddenMethodType.getErased();
}
OverrideChecker overrideChecker =
createOverrideChecker(
overriderTree,
overriderMethodType,
overriderType,
overriderMethodType.getReturnType(),
overriddenMethodType,
overriddenType,
overriddenMethodType.getReturnType());
return overrideChecker.checkOverride();
}
/**
* 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<AnnotatedTypeMirror, AnnotatedExecutableType> result =
atypeFactory.getFnInterfaceFromTree(memberReferenceTree);
// The type to which the member reference is assigned -- also known as the target type of the
// reference.
AnnotatedTypeMirror functionalInterface = result.first;
// The type of the single method that is declared by the functional interface.
AnnotatedExecutableType functionType = result.second;
// ========= Overriding Type =========
// 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.
// The type of the expression or type use, <expression>::method or <type use>::method.
final ExpressionTree qualifierExpression = memberReferenceTree.getQualifierExpression();
final ReferenceKind memRefKind = ((JCMemberReference) memberReferenceTree).kind;
AnnotatedTypeMirror enclosingType;
if (memberReferenceTree.getMode() == ReferenceMode.NEW
|| memRefKind == ReferenceKind.UNBOUND
|| memRefKind == ReferenceKind.STATIC) {
// The "qualifier expression" is a type tree.
enclosingType = atypeFactory.getAnnotatedTypeFromTypeTree(qualifierExpression);
} else {
// The "qualifier expression" is an expression.
enclosingType = atypeFactory.getAnnotatedType(qualifierExpression);
}
// ========= Overriding Executable =========
// The ::method element, see JLS 15.13.1 Compile-Time Declaration of a Method Reference
ExecutableElement compileTimeDeclaration =
(ExecutableElement) TreeUtils.elementFromTree(memberReferenceTree);
if (enclosingType.getKind() == TypeKind.DECLARED
&& ((AnnotatedDeclaredType) enclosingType).wasRaw()) {
if (memRefKind == ReferenceKind.UNBOUND) {
// The method reference is of the form: Type # instMethod and Type is a raw type.
// If the first parameter of the function type, p1, is a subtype of type, then type should
// be p1. This has the effect of "inferring" the class type parameter.
AnnotatedTypeMirror p1 = functionType.getParameterTypes().get(0);
TypeMirror asSuper =
TypesUtils.asSuper(
p1.getUnderlyingType(),
enclosingType.getUnderlyingType(),
atypeFactory.getProcessingEnv());
if (asSuper != null) {
enclosingType = AnnotatedTypes.asSuper(atypeFactory, p1, enclosingType);
}
}
// else method reference is something like ArrayList::new
// TODO: Use diamond, <>, inference to infer the class type arguments.
// For now this case is skipped below in checkMethodReferenceInference.
}
// The type of the compileTimeDeclaration if it were invoked with a receiver expression
// of type {@code type}
AnnotatedExecutableType invocationType =
atypeFactory.methodFromUse(memberReferenceTree, compileTimeDeclaration, enclosingType)
.executableType;
if (checkMethodReferenceInference(memberReferenceTree, invocationType, enclosingType)) {
// Type argument inference is required, skip check.
// #checkMethodReferenceInference issued a warning.
return true;
}
// This needs to be done before invocationType.getReturnType() and
// functionType.getReturnType()
if (invocationType.getTypeVariables().isEmpty() && !functionType.getTypeVariables().isEmpty()) {
functionType = functionType.getErased();
}
// Use the function type's parameters to resolve polymorphic qualifiers.
QualifierPolymorphism poly = atypeFactory.getQualifierPolymorphism();
poly.resolve(functionType, invocationType);
AnnotatedTypeMirror invocationReturnType;
if (compileTimeDeclaration.getKind() == ElementKind.CONSTRUCTOR) {
if (enclosingType.getKind() == TypeKind.ARRAY) {
// Special casing for the return of array constructor
invocationReturnType = enclosingType;
} else {
invocationReturnType =
atypeFactory.getResultingTypeOfConstructorMemberReference(
memberReferenceTree, invocationType);
}
} else {
invocationReturnType = invocationType.getReturnType();
}
AnnotatedTypeMirror functionTypeReturnType = functionType.getReturnType();
if (functionTypeReturnType.getKind() == TypeKind.VOID) {
// If the functional interface return type is void, the overriding return type doesn't matter.
functionTypeReturnType = invocationReturnType;
}
if (functionalInterface.getKind() == TypeKind.DECLARED) {
// Check the member reference as if invocationType overrides functionType.
OverrideChecker overrideChecker =
createOverrideChecker(
memberReferenceTree,
invocationType,
enclosingType,
invocationReturnType,
functionType,
(AnnotatedDeclaredType) functionalInterface,
functionTypeReturnType);
return overrideChecker.checkOverride();
} else {
// If the functionalInterface is not a declared type, it must be an uninferred wildcard.
// In that case, only return false if uninferred type arguments should not be ignored.
return !atypeFactory.ignoreUninferredTypeArguments;
}
}
/** Check if method reference type argument inference is required. Issue an error if it is. */
private boolean checkMethodReferenceInference(
MemberReferenceTree memberReferenceTree,
AnnotatedExecutableType invocationType,
AnnotatedTypeMirror type) {
// 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.
boolean requiresInference = false;
// If the function to which the member reference refers is generic, but the member
// reference does not provide method type arguments, then Java 8 inference is required.
// Issue 979.
if (!invocationType.getTypeVariables().isEmpty()
&& (memberReferenceTree.getTypeArguments() == null
|| memberReferenceTree.getTypeArguments().isEmpty())) {
// Method type args
requiresInference = true;
} else if (memberReferenceTree.getMode() == ReferenceMode.NEW) {
if (type.getKind() == TypeKind.DECLARED && ((AnnotatedDeclaredType) type).wasRaw()) {
// Class type args
requiresInference = true;
}
}
if (requiresInference) {
if (checker.hasOption("conservativeUninferredTypeArguments")) {
checker.reportWarning(memberReferenceTree, "methodref.inference.unimplemented");
}
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.
*/
public class OverrideChecker {
/** The declaration of an overriding method. */
protected final Tree overriderTree;
/** True if {@link #overriderTree} is a MEMBER_REFERENCE. */
protected final boolean isMethodReference;
/** The type of the overriding method. */
protected final AnnotatedExecutableType overrider;
/** The subtype that declares the overriding method. */
protected final AnnotatedTypeMirror overriderType;
/** The type of the overridden method. */
protected final AnnotatedExecutableType overridden;
/** The supertype that declares the overridden method. */
protected final AnnotatedDeclaredType overriddenType;
/** The teturn type of the overridden method. */
protected final AnnotatedTypeMirror overriddenReturnType;
/** The return type of the overriding method. */
protected final AnnotatedTypeMirror overriderReturnType;
/**
* 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 overriderType the type enclosing the overrider method, usually an
* AnnotatedDeclaredType; for Method References may be something else
* @param overriderReturnType 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
*/
public OverrideChecker(
Tree overriderTree,
AnnotatedExecutableType overrider,
AnnotatedTypeMirror overriderType,
AnnotatedTypeMirror overriderReturnType,
AnnotatedExecutableType overridden,
AnnotatedDeclaredType overriddenType,
AnnotatedTypeMirror overriddenReturnType) {
this.overriderTree = overriderTree;
this.overrider = overrider;
this.overriderType = overriderType;
this.overridden = overridden;
this.overriddenType = overriddenType;
this.overriddenReturnType = overriddenReturnType;
this.overriderReturnType = overriderReturnType;
this.isMethodReference = 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 (isMethodReference) {
result &= checkMemberReferenceReceivers();
} else {
result &= checkReceiverOverride();
}
checkPreAndPostConditions();
checkPurity();
return result;
}
/** Check that an override respects purity. */
private void checkPurity() {
String msgKey = isMethodReference ? "purity.methodref" : "purity.overriding";
// check purity annotations
EnumSet<Pure.Kind> superPurity =
PurityUtils.getPurityKinds(atypeFactory, overridden.getElement());
EnumSet<Pure.Kind> subPurity =
PurityUtils.getPurityKinds(atypeFactory, overrider.getElement());
if (!subPurity.containsAll(superPurity)) {
checker.reportError(
overriderTree,
msgKey,
overriderType,
overrider,
overriddenType,
overridden,
subPurity,
superPurity);
}
}
/** Checks that overrides obey behavioral subtyping. */
private void checkPreAndPostConditions() {
String msgKey = isMethodReference ? "methodref" : "override";
if (isMethodReference) {
// TODO: Support postconditions and method references.
// The parse context always expects instance methods, but method references can be static.
return;
}
ContractsFromMethod contractsUtils = atypeFactory.getContractsFromMethod();
// Check preconditions
Set<Precondition> superPre = contractsUtils.getPreconditions(overridden.getElement());
Set<Precondition> subPre = contractsUtils.getPreconditions(overrider.getElement());
Set<Pair<JavaExpression, AnnotationMirror>> superPre2 =
parseAndLocalizeContracts(superPre, overridden);
Set<Pair<JavaExpression, AnnotationMirror>> subPre2 =
parseAndLocalizeContracts(subPre, overrider);
@SuppressWarnings("compilermessages")
@CompilerMessageKey String premsg = "contracts.precondition." + msgKey;
checkContractsSubset(overriderType, overriddenType, subPre2, superPre2, premsg);
// Check postconditions
Set<Postcondition> superPost = contractsUtils.getPostconditions(overridden.getElement());
Set<Postcondition> subPost = contractsUtils.getPostconditions(overrider.getElement());
Set<Pair<JavaExpression, AnnotationMirror>> superPost2 =
parseAndLocalizeContracts(superPost, overridden);
Set<Pair<JavaExpression, AnnotationMirror>> subPost2 =
parseAndLocalizeContracts(subPost, overrider);
@SuppressWarnings("compilermessages")
@CompilerMessageKey String postmsg = "contracts.postcondition." + msgKey;
checkContractsSubset(overriderType, overriddenType, superPost2, subPost2, postmsg);
// Check conditional postconditions
Set<ConditionalPostcondition> superCPost =
contractsUtils.getConditionalPostconditions(overridden.getElement());
Set<ConditionalPostcondition> subCPost =
contractsUtils.getConditionalPostconditions(overrider.getElement());
// consider only 'true' postconditions
Set<Postcondition> superCPostTrue = filterConditionalPostconditions(superCPost, true);
Set<Postcondition> subCPostTrue = filterConditionalPostconditions(subCPost, true);
Set<Pair<JavaExpression, AnnotationMirror>> superCPostTrue2 =
parseAndLocalizeContracts(superCPostTrue, overridden);
Set<Pair<JavaExpression, AnnotationMirror>> subCPostTrue2 =
parseAndLocalizeContracts(subCPostTrue, overrider);
@SuppressWarnings("compilermessages")
@CompilerMessageKey String posttruemsg = "contracts.conditional.postcondition.true." + msgKey;
checkContractsSubset(
overriderType, overriddenType, superCPostTrue2, subCPostTrue2, posttruemsg);
// consider only 'false' postconditions
Set<Postcondition> superCPostFalse = filterConditionalPostconditions(superCPost, false);
Set<Postcondition> subCPostFalse = filterConditionalPostconditions(subCPost, false);
Set<Pair<JavaExpression, AnnotationMirror>> superCPostFalse2 =
parseAndLocalizeContracts(superCPostFalse, overridden);
Set<Pair<JavaExpression, AnnotationMirror>> subCPostFalse2 =
parseAndLocalizeContracts(subCPostFalse, overrider);
@SuppressWarnings("compilermessages")
@CompilerMessageKey String postfalsemsg = "contracts.conditional.postcondition.false." + msgKey;
checkContractsSubset(
overriderType, overriddenType, superCPostFalse2, subCPostFalse2, postfalsemsg);
}
private boolean checkMemberReferenceReceivers() {
JCTree.JCMemberReference memberTree = (JCTree.JCMemberReference) overriderTree;
if (overriderType.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.reportError(
overriderTree,
"methodref.receiver",
overriderReceiver,
overriddenReceiver,
overriderType,
overrider,
overriddenType,
overridden);
}
return success;
}
// The rest act like method invocations
AnnotatedTypeMirror receiverDecl;
AnnotatedTypeMirror receiverArg;
switch (memberTree.kind) {
case UNBOUND:
throw new BugInCF("Case UNBOUND should already be handled.");
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 = overriderType;
break;
case IMPLICIT_INNER:
// JLS 15.13.1 "It is a compile-time error if the method reference expression is
// of the form ClassType :: [TypeArguments] new and a compile-time error would
// occur when determining an enclosing instance for ClassType as specified in
// 15.9.2 (treating the method reference expression as if it were an unqualified
// class instance creation expression)."
// So a member reference can only refer to an inner class constructor if a type
// that encloses the inner class can be found. So either "this" is that
// enclosing type or "this" has an enclosing type that is that type.
receiverDecl = overrider.getReceiverType();
receiverArg = atypeFactory.getSelfType(memberTree);
while (!TypesUtils.isErasedSubtype(
receiverArg.getUnderlyingType(), receiverDecl.getUnderlyingType(), types)) {
receiverArg = ((AnnotatedDeclaredType) receiverArg).getEnclosingType();
}
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.reportError(
overriderTree,
"methodref.receiver.bound",
receiverArg,
receiverDecl,
receiverArg,
overriderType,
overrider);
}
return success;
}
/**
* Issue an "override.receiver" error if the receiver override is not valid.
*
* @return true if the override is legal
*/
protected boolean checkReceiverOverride() {
AnnotatedDeclaredType overriderReceiver = overrider.getReceiverType();
AnnotatedDeclaredType overriddenReceiver = overridden.getReceiverType();
QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
// 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. So, just check primary annotations.
// TODO: this will need to be improved for generic receivers.
Set<AnnotationMirror> overriderAnnos = overriderReceiver.getAnnotations();
Set<AnnotationMirror> overriddenAnnos = overriddenReceiver.getAnnotations();
if (!qualifierHierarchy.isSubtype(overriddenAnnos, overriderAnnos)) {
Set<AnnotationMirror> declaredAnnos =
atypeFactory.getTypeDeclarationBounds(overriderType.getUnderlyingType());
if (qualifierHierarchy.isSubtype(overriderAnnos, declaredAnnos)
&& qualifierHierarchy.isSubtype(declaredAnnos, overriderAnnos)) {
// All the type of an object must be no higher than its upper bound. So if the receiver is
// annotated with the upper bound qualifiers, then the override is safe.
return true;
}
FoundRequired pair = FoundRequired.of(overriderReceiver, overriddenReceiver);
checker.reportError(
overriderTree,
"override.receiver",
pair.found,
pair.required,
overriderType,
overrider,
overriddenType,
overridden);
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/se11/html/jls-15.html#jls-15.13.1
if (isMethodReference) {
// 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) {
if (success && !showchecks) {
return;
}
String msgKey = isMethodReference ? "methodref.param" : "override.param";
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 (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),
overrider,
overriderType,
index,
overriderParams.get(index).toString(),
overridden,
overriddenType,
index,
overriddenParams.get(index).toString());
}
if (!success) {
FoundRequired pair =
FoundRequired.of(overriderParams.get(index), overriddenParams.get(index));
checker.reportError(
posTree,
msgKey,
overrider.getElement().getParameters().get(index).toString(),
pair.found,
pair.required,
overriderType,
overrider,
overriddenType,
overridden);
}
}
/**
* Returns true if the return type of the overridden method is a subtype of the return type of
* the overriding method.
*
* @return true if the return type is correct
*/
private boolean checkReturn() {
if ((overriderReturnType.getKind() == TypeKind.VOID)) {
// Nothing to check.
return true;
}
final TypeHierarchy typeHierarchy = atypeFactory.getTypeHierarchy();
boolean success = typeHierarchy.isSubtype(overriderReturnType, overriddenReturnType);
if (!success) {
// 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
success = testTypevarContainment(overriderReturnType, overriddenReturnType);
}
// Sometimes 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.
if (!success
&& isMethodReference
&& TypesUtils.isCaptured(overriddenReturnType.getUnderlyingType())) {
if (ElementUtils.isMethod(
overridden.getElement(), functionApply, atypeFactory.getProcessingEnv())) {
success =
typeHierarchy.isSubtype(
overriderReturnType,
((AnnotatedTypeVariable) overriddenReturnType).getUpperBound());
}
}
checkReturnMsg(success);
return success;
}
/**
* Issue an error message or log message about checking an overriding return type.
*
* @param success whether the check succeeded or failed
*/
private void checkReturnMsg(boolean success) {
if (success && !showchecks) {
return;
}
String msgKey = isMethodReference ? "methodref.return" : "override.return";
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 (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),
overrider,
overriderType,
overrider.getReturnType().toString(),
overridden,
overriddenType,
overridden.getReturnType().toString());
}
if (!success) {
FoundRequired pair = FoundRequired.of(overriderReturnType, overriddenReturnType);
checker.reportError(
posTree,
msgKey,
pair.found,
pair.required,
overriderType,
overrider,
overriddenType,
overridden);
}
}
}
/**
* 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)}
*
* @param conditionalPostconditions each is a ConditionalPostcondition
* @param b the value required for the {@code result} element
* @return all the given conditional postconditions whose {@code result} is {@code b}
*/
private Set<Postcondition> filterConditionalPostconditions(
Set<ConditionalPostcondition> conditionalPostconditions, boolean b) {
if (conditionalPostconditions.isEmpty()) {
return Collections.emptySet();
}
Set<Postcondition> result = new LinkedHashSet<>(conditionalPostconditions.size());
for (Contract c : conditionalPostconditions) {
ConditionalPostcondition p = (ConditionalPostcondition) c;
if (p.resultValue == b) {
result.add(new Postcondition(p.expressionString, p.annotation, p.contractAnnotation));
}
}
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.
*
* <p>This uses field {@link #visitorState} to determine where to issue an error message.
*
* @param overriderType the subtype
* @param overriddenType the supertype
* @param mustSubset annotations that should be weaker
* @param set anontations that should be stronger
* @param messageKey message key for error messages
*/
private void checkContractsSubset(
AnnotatedTypeMirror overriderType,
AnnotatedDeclaredType overriddenType,
Set<Pair<JavaExpression, AnnotationMirror>> mustSubset,
Set<Pair<JavaExpression, AnnotationMirror>> set,
@CompilerMessageKey String messageKey) {
for (Pair<JavaExpression, AnnotationMirror> weak : mustSubset) {
boolean found = false;
for (Pair<JavaExpression, AnnotationMirror> strong : set) {
// are we looking at a contract of the same receiver?
if (weak.first.equals(strong.first)) {
// check subtyping relationship of annotations
QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
if (qualifierHierarchy.isSubtype(strong.second, weak.second)) {
found = true;
break;
}
}
}
if (!found) {
MethodTree methodDeclTree = visitorState.getMethodTree();
String overriddenTypeString = overriddenType.getUnderlyingType().asElement().toString();
String overriderTypeString;
if (overriderType.getKind() == TypeKind.DECLARED) {
DeclaredType overriderTypeMirror =
((AnnotatedDeclaredType) overriderType).getUnderlyingType();
overriderTypeString = overriderTypeMirror.asElement().toString();
} else {
overriderTypeString = overriderType.toString();
}
// weak.second is the AnnotationMirror that is too strong. It might be from the
// precondition or the postcondition.
// These are the annotations that are too weak.
StringJoiner strongRelevantAnnos = new StringJoiner(" ").setEmptyValue("no information");
for (Pair<JavaExpression, AnnotationMirror> strong : set) {
if (weak.first.equals(strong.first)) {
strongRelevantAnnos.add(strong.second.toString());
}
}
Object overriddenAnno;
Object overriderAnno;
if (messageKey.contains(".precondition.")) {
overriddenAnno = strongRelevantAnnos;
overriderAnno = weak.second;
} else {
overriddenAnno = weak.second;
overriderAnno = strongRelevantAnnos;
}
checker.reportError(
methodDeclTree,
messageKey,
weak.first,
methodDeclTree.getName(),
overriddenTypeString,
overriddenAnno,
overriderTypeString,
overriderAnno);
}
}
}
/**
* Localizes some contracts -- that is, viewpoint-adapts them to some method body, according to
* the value of {@link #visitorState}.
*
* <p>The input is a set of {@link Contract}s, each of which contains an expression string and an
* annotation. In a {@link Contract}, Java expressions are exactly as written in source code, not
* standardized or viewpoint-adapted.
*
* <p>The output is a set of pairs of {@link JavaExpression} (parsed expression string) and
* standardized annotation (with respect to the path of {@link #visitorState}. This method
* discards any contract whose expression cannot be parsed into a JavaExpression.
*
* @param contractSet a set of contracts
* @param methodType the type of the method that the contracts are for
* @return pairs of (expression, AnnotationMirror), which are localized contracts
*/
private Set<Pair<JavaExpression, AnnotationMirror>> parseAndLocalizeContracts(
Set<? extends Contract> contractSet, AnnotatedExecutableType methodType) {
if (contractSet.isEmpty()) {
return Collections.emptySet();
}
// This is the path to a place where the contract is being used, which might or might not be
// where the contract was defined. For example, methodTree might be an overriding
// definition, and the contract might be for a superclass.
MethodTree methodTree = visitorState.getMethodTree();
StringToJavaExpression stringToJavaExpr =
expression -> {
JavaExpression javaExpr =
StringToJavaExpression.atMethodDecl(expression, methodType.getElement(), checker);
// methodType.getElement() is not necessarily the same method as methodTree, so
// viewpoint-adapt it to methodTree.
return javaExpr.atMethodBody(methodTree);
};
Set<Pair<JavaExpression, AnnotationMirror>> result = new HashSet<>(contractSet.size());
for (Contract p : contractSet) {
String expressionString = p.expressionString;
AnnotationMirror annotation =
p.viewpointAdaptDependentTypeAnnotation(atypeFactory, stringToJavaExpr, methodTree);
JavaExpression exprJe;
try {
// TODO: currently, these expressions are parsed many times.
// This could be optimized to store the result the first time.
// (same for other annotations)
exprJe = stringToJavaExpr.toJavaExpression(expressionString);
} catch (JavaExpressionParseException e) {
// report errors here
checker.report(methodTree, e.getDiagMessage());
continue;
}
result.add(Pair.of(exprJe, annotation));
}
return result;
}
/**
* Call this only when the current path is an identifier.
*
* @return the enclosing member select, or null if the identifier is not the field in a member
* selection
*/
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;
}
}
/**
* Returns the statement that encloses the given one.
*
* @param tree an AST node that is on the current path
* @return the statement that encloses the given one
*/
protected Tree enclosingStatement(@FindDistinct 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);
checkAccessAllowed(elem, receiver, tree);
}
/**
* Issues an error if access not allowed, based on an @Unused annotation.
*
* @param field the field to be accessed, whose declaration might be annotated by @Unused. It can
* also be (for example) {@code this}, in which case {@code receiverType} is null.
* @param receiverType the type of the expression whose field is accessed; null if the field is
* static
* @param accessTree the access expression
*/
protected void checkAccessAllowed(
Element field, AnnotatedTypeMirror receiverType, @FindDistinct ExpressionTree accessTree) {
AnnotationMirror unused = atypeFactory.getDeclAnnotation(field, Unused.class);
if (unused == null) {
return;
}
String when = AnnotationUtils.getElementValueClassName(unused, unusedWhenElement).toString();
// TODO: Don't just look at the receiver type, but at the declaration annotations on the
// receiver. (That will enable handling type annotations that are not part of the type
// system being checked.)
// TODO: This requires exactly the same type qualifier, but it should permit subqualifiers.
if (!AnnotationUtils.containsSameByName(receiverType.getAnnotations(), when)) {
return;
}
Tree tree = this.enclosingStatement(accessTree);
if (tree != null
&& tree.getKind() == Tree.Kind.ASSIGNMENT
&& ((AssignmentTree) tree).getVariable() == accessTree
&& ((AssignmentTree) tree).getExpression().getKind() == Tree.Kind.NULL_LITERAL) {
// Assigning unused to null is OK.
return;
}
checker.reportError(accessTree, "unallowed.access", field, receiverType);
}
/**
* Tests that the qualifiers present on {@code useType} are valid qualifiers, given the qualifiers
* on the declaration of the type, {@code 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}. If a
* type system makes exceptions to this rule, its implementation should override this method.
*
* <p>This method is not called if {@link
* BaseTypeValidator#shouldCheckTopLevelDeclaredOrPrimitiveType(AnnotatedTypeMirror, Tree)}
* returns false -- by default, it is not called on the top level for locals and expressions. To
* enforce a type validity property everywhere, override methods such as {@link
* BaseTypeValidator#visitDeclared} rather than this method.
*
* @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) {
// Don't use isSubtype(ATM, ATM) because it will return false if the types have qualifier
// parameters.
Set<? extends AnnotationMirror> tops = atypeFactory.getQualifierHierarchy().getTopAnnotations();
Set<AnnotationMirror> upperBounds =
atypeFactory
.getQualifierUpperBounds()
.getBoundQualifiers(declarationType.getUnderlyingType());
for (AnnotationMirror top : tops) {
AnnotationMirror upperBound =
atypeFactory.getQualifierHierarchy().findAnnotationInHierarchy(upperBounds, top);
AnnotationMirror qualifier = useType.getAnnotationInHierarchy(top);
if (!atypeFactory.getQualifierHierarchy().isSubtype(qualifier, upperBound)) {
return false;
}
}
return true;
}
/**
* Tests that the qualifiers present on the primitive type are valid.
*
* @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) {
Set<AnnotationMirror> bounds = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType());
return atypeFactory.getQualifierHierarchy().isSubtype(type.getAnnotations(), bounds);
}
/**
* 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.
*
* @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) {
Set<AnnotationMirror> bounds = atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType());
return atypeFactory.getQualifierHierarchy().isSubtype(type.getAnnotations(), bounds);
}
/**
* 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
* @return true if the type is valid
*/
protected boolean validateType(Tree tree, AnnotatedTypeMirror type) {
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);
Element elm = TreeUtils.elementFromTree(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;
}
}