blob: 94bae968649c553a9d54ef189feb76edaa7015c0 [file] [log] [blame]
package org.checkerframework.checker.mustcall;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.mustcall.qual.InheritableMustCall;
import org.checkerframework.checker.mustcall.qual.MustCallAlias;
import org.checkerframework.checker.mustcall.qual.NotOwning;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
/**
* The visitor for the Must Call Checker. This visitor is similar to BaseTypeVisitor, but overrides
* methods that don't work well with the MustCall type hierarchy because it doesn't use the top type
* as the default type.
*/
public class MustCallVisitor extends BaseTypeVisitor<MustCallAnnotatedTypeFactory> {
/**
* Creates a new MustCallVisitor.
*
* @param checker the type-checker associated with this visitor
*/
public MustCallVisitor(BaseTypeChecker checker) {
super(checker);
}
@Override
public Void visitReturn(ReturnTree node, Void p) {
// Only check return types if ownership is being transferred.
if (!checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP)) {
MethodTree enclosingMethod = TreePathUtil.enclosingMethod(this.getCurrentPath());
// enclosingMethod is null if this return site is inside a lambda. TODO: handle lambdas more
// precisely?
if (enclosingMethod != null) {
ExecutableElement methodElt = TreeUtils.elementFromDeclaration(enclosingMethod);
AnnotationMirror notOwningAnno = atypeFactory.getDeclAnnotation(methodElt, NotOwning.class);
if (notOwningAnno != null) {
// Skip return type subtyping check, because not-owning pointer means Object Construction
// Checker won't check anyway.
return null;
}
}
}
return super.visitReturn(node, p);
}
@Override
protected boolean validateType(Tree tree, AnnotatedTypeMirror type) {
if (TreeUtils.isClassTree(tree)) {
Element classEle = TreeUtils.elementFromDeclaration((ClassTree) tree);
AnnotationMirror inheritableMustCall =
atypeFactory.getDeclAnnotation(classEle, InheritableMustCall.class);
if (inheritableMustCall != null) {
AnnotationMirror explict = atypeFactory.fromElement(classEle).getAnnotation();
if (explict != null) {
List<String> mustCallVal =
AnnotationUtils.getElementValueArray(
inheritableMustCall, atypeFactory.inheritableMustCallValueElement, String.class);
AnnotationMirror inheritedMCAnno = atypeFactory.createMustCall(mustCallVal);
// Issue an error if there is an inconsistent, user-written @MustCall annotation.
AnnotationMirror writtenMCAnno = type.getAnnotation();
if (writtenMCAnno != null
&& !atypeFactory.getQualifierHierarchy().isSubtype(inheritedMCAnno, writtenMCAnno)) {
checker.reportError(
tree,
"inconsistent.mustcall.subtype",
classEle.getSimpleName(),
writtenMCAnno,
inheritableMustCall);
return false;
}
}
}
}
return super.validateType(tree, type);
}
@Override
public boolean isValidUse(
AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) {
// MustCallAlias annotations are always permitted on type uses, because these will be validated
// by the Object Construction Checker's -AcheckMustCall algorithm.
AnnotatedDeclaredType useTypeCopy = useType.deepCopy();
if (!checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES)) {
useTypeCopy.removeAnnotationByClass(MustCallAlias.class);
}
return super.isValidUse(declarationType, useTypeCopy, tree);
}
@Override
protected boolean skipReceiverSubtypeCheck(
MethodInvocationTree node,
AnnotatedTypeMirror methodDefinitionReceiver,
AnnotatedTypeMirror methodCallReceiver) {
// It does not make sense for receivers to have must-call obligations. If the receiver of a
// method were to have a non-empty must-call obligation, then actually this method should
// be part of the must-call annotation on the class declaration! So skipping this check is
// always sound.
return true;
}
/**
* This boolean is used to communicate between different levels of the common assignment check
* whether a given check is being carried out on a (pseudo-)assignment to a resource variable. In
* those cases, close doesn't need to be considered when doing the check, since close will always
* be called by Java.
*
* <p>The check for whether the LHS is a resource variable can only be carried out on the element,
* but the effect needs to happen at the stage where the type is available (i.e. close needs to be
* removed from the type). Thus, this variable is used to communicate that a resource variable was
* detected on the LHS.
*/
private boolean commonAssignmentCheckOnResourceVariable = false;
/**
* Mark (using the {@link #commonAssignmentCheckOnResourceVariable} field of this class) any
* assignments where the LHS is a resource variable, so that close doesn't need to be considered.
* See {@link #commonAssignmentCheck(AnnotatedTypeMirror, AnnotatedTypeMirror, Tree, String,
* Object...)} for the code that uses and removes the mark.
*/
@Override
protected void commonAssignmentCheck(
Tree varTree,
ExpressionTree valueExp,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
if (TreeUtils.elementFromTree(varTree).getKind() == ElementKind.RESOURCE_VARIABLE) {
commonAssignmentCheckOnResourceVariable = true;
}
super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs);
}
/**
* Iff the LHS is a resource variable, then {@link #commonAssignmentCheckOnResourceVariable} will
* be true. This method guarantees that {@link #commonAssignmentCheckOnResourceVariable} will be
* false when it returns.
*/
@Override
protected void commonAssignmentCheck(
AnnotatedTypeMirror varType,
AnnotatedTypeMirror valueType,
Tree valueTree,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
if (commonAssignmentCheckOnResourceVariable) {
commonAssignmentCheckOnResourceVariable = false;
// The LHS has been marked as a resource variable. Skip the standard common assignment check;
// instead do a check that does not include "close".
AnnotationMirror varAnno = varType.getAnnotationInHierarchy(atypeFactory.TOP);
AnnotationMirror valAnno = valueType.getAnnotationInHierarchy(atypeFactory.TOP);
if (atypeFactory
.getQualifierHierarchy()
.isSubtype(atypeFactory.withoutClose(valAnno), atypeFactory.withoutClose(varAnno))) {
return;
}
// Note that in this case, the rest of the common assignment check should fail (barring an
// exception). Control falls through here to avoid duplicating error-issuing code.
}
// commonAssignmentCheckOnResourceVariable is already false, so no need to set it.
super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
}
/**
* This method typically issues a warning if the result type of the constructor is not top,
* because in top-default type systems that indicates a potential problem. The Must Call Checker
* does not need this warning, because it expects the type of all constructors to be {@code
* MustCall({})} (by default) or some other {@code MustCall} type, not the top type.
*
* <p>Instead, this method checks that the result type of a constructor is a supertype of the
* declared type on the class, if one exists.
*
* @param constructorType AnnotatedExecutableType for the constructor
* @param constructorElement element that declares the constructor
*/
@Override
protected void checkConstructorResult(
AnnotatedExecutableType constructorType, ExecutableElement constructorElement) {
AnnotatedTypeMirror defaultType =
atypeFactory.getAnnotatedType(ElementUtils.enclosingTypeElement(constructorElement));
AnnotationMirror defaultAnno = defaultType.getAnnotationInHierarchy(atypeFactory.TOP);
AnnotationMirror resultAnno =
constructorType.getReturnType().getAnnotationInHierarchy(atypeFactory.TOP);
if (!atypeFactory.getQualifierHierarchy().isSubtype(defaultAnno, resultAnno)) {
checker.reportError(
constructorElement, "inconsistent.constructor.type", resultAnno, defaultAnno);
}
}
/**
* Change the default for exception parameter lower bounds to bottom (the default), to prevent
* false positives. This is unsound; see the discussion on
* https://github.com/typetools/checker-framework/issues/3839.
*
* <p>TODO: change checking of throws clauses to require that the thrown exception
* is @MustCall({}). This would probably eliminate most of the same false positives, without
* adding undue false positives.
*
* @return a set containing only the @MustCall({}) annotation
*/
@Override
protected Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotations() {
return Collections.singleton(atypeFactory.BOTTOM);
}
/**
* Does not issue any warnings.
*
* <p>This implementation prevents recursing into annotation arguments. Annotation arguments are
* literals, which don't have must-call obligations.
*
* <p>Annotation arguments are treated as return locations for the purposes of defaulting, rather
* than parameter locations. This causes them to default incorrectly when the annotation is
* defined in bytecode. See https://github.com/typetools/checker-framework/issues/3178 for an
* explanation of why this is necessary to avoid false positives.
*/
@Override
public Void visitAnnotation(AnnotationTree node, Void p) {
return null;
}
}