org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory Maven / Gradle / Ivy
Show all versions of checker Show documentation
package org.checkerframework.checker.mustcall;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor;
import org.checkerframework.checker.mustcall.qual.InheritableMustCall;
import org.checkerframework.checker.mustcall.qual.MustCall;
import org.checkerframework.checker.mustcall.qual.MustCallAlias;
import org.checkerframework.checker.mustcall.qual.MustCallUnknown;
import org.checkerframework.checker.mustcall.qual.Owning;
import org.checkerframework.checker.mustcall.qual.PolyMustCall;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.cfg.block.Block;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.QualifierUpperBounds;
import org.checkerframework.framework.type.SubtypeIsSubsetQualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.type.typeannotator.DefaultQualifierForUseTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationMirrorSet;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeSystemError;
import org.checkerframework.javacutil.TypesUtils;
/**
* The annotated type factory for the Must Call Checker. Primarily responsible for the subtyping
* rules between @MustCall annotations.
*/
public class MustCallAnnotatedTypeFactory extends BaseAnnotatedTypeFactory
implements CreatesMustCallForElementSupplier {
/** The {@code @}{@link MustCallUnknown} annotation. */
public final AnnotationMirror TOP;
/** The {@code @}{@link MustCall}{@code ()} annotation. It is the default in unannotated code. */
public final AnnotationMirror BOTTOM;
/** The {@code @}{@link PolyMustCall} annotation. */
public final AnnotationMirror POLY;
/**
* Map from trees representing expressions to the temporary variables that represent them in the
* store.
*
* Consider the following code, adapted from Apache Zookeeper:
*
*
* sock = SocketChannel.open();
* sock.socket().setSoLinger(false, -1);
*
*
* This code is safe from resource leaks: sock is an unconnected socket and therefore has no
* must-call obligation. The expression sock.socket() similarly has no must-call obligation
* because it is a resource alias, but without a temporary variable that represents that
* expression in the store, the resource leak checker wouldn't be able to determine that.
*
* These temporary variables are only created once---here---but are used by all three parts of
* the resource leak checker by calling {@link #getTempVar(Node)}. The temporary variables are
* shared in the same way that subcheckers share CFG structure; see {@link
* #getSharedCFGForTree(Tree)}.
*/
/*package-private*/ final IdentityHashMap tempVars =
new IdentityHashMap<>(100);
/** The MustCall.value field/element. */
private final ExecutableElement mustCallValueElement =
TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv);
/** The InheritableMustCall.value field/element. */
/*package-private*/ final ExecutableElement inheritableMustCallValueElement =
TreeUtils.getMethod(InheritableMustCall.class, "value", 0, processingEnv);
/** The CreatesMustCallFor.List.value field/element. */
private final ExecutableElement createsMustCallForListValueElement =
TreeUtils.getMethod(CreatesMustCallFor.List.class, "value", 0, processingEnv);
/** The CreatesMustCallFor.value field/element. */
private final ExecutableElement createsMustCallForValueElement =
TreeUtils.getMethod(CreatesMustCallFor.class, "value", 0, processingEnv);
/** True if -AnoLightweightOwnership was passed on the command line. */
private final boolean noLightweightOwnership;
/**
* Creates a MustCallAnnotatedTypeFactory.
*
* @param checker the checker associated with this type factory
*/
public MustCallAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
TOP = AnnotationBuilder.fromClass(elements, MustCallUnknown.class);
BOTTOM = createMustCall(Collections.emptyList());
POLY = AnnotationBuilder.fromClass(elements, PolyMustCall.class);
addAliasedTypeAnnotation(InheritableMustCall.class, MustCall.class, true);
if (!checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES)) {
// In NO_RESOURCE_ALIASES mode, all @MustCallAlias annotations are ignored.
addAliasedTypeAnnotation(MustCallAlias.class, POLY);
}
noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP);
this.postInit();
}
@Override
public void setRoot(@Nullable CompilationUnitTree root) {
super.setRoot(root);
// TODO: This should probably be guarded by isSafeToClearSharedCFG from
// GenericAnnotatedTypeFactory, but this works here because we know the Must Call Checker is
// always the first subchecker that's sharing tempvars.
tempVars.clear();
}
@Override
protected Set> createSupportedTypeQualifiers() {
// Explicitly name the qualifiers, in order to exclude @MustCallAlias.
return new LinkedHashSet<>(
Arrays.asList(MustCall.class, MustCallUnknown.class, PolyMustCall.class));
}
@Override
protected TreeAnnotator createTreeAnnotator() {
return new ListTreeAnnotator(super.createTreeAnnotator(), new MustCallTreeAnnotator(this));
}
@Override
protected TypeAnnotator createTypeAnnotator() {
return new ListTypeAnnotator(super.createTypeAnnotator(), new MustCallTypeAnnotator(this));
}
/**
* Returns a {@literal @}MustCall annotation that is like the input, but it does not have "close".
* Returns the argument annotation mirror (not a new one) if the argument doesn't have "close" as
* one of its elements.
*
* If the argument is null, returns bottom.
*
* @param anno a MustCall annotation
* @return a MustCall annotation that does not have "close" as one of its values, but is otherwise
* identical to anno
*/
public AnnotationMirror withoutClose(@Nullable AnnotationMirror anno) {
if (anno == null || AnnotationUtils.areSame(anno, BOTTOM)) {
return BOTTOM;
} else if (!AnnotationUtils.areSameByName(
anno, "org.checkerframework.checker.mustcall.qual.MustCall")) {
return anno;
}
List values =
AnnotationUtils.getElementValueArray(anno, mustCallValueElement, String.class);
// Use `removeAll` because `remove` only removes the first occurrence.
if (values.removeAll(Collections.singletonList("close"))) {
return createMustCall(values);
} else {
return anno;
}
}
/** Treat non-owning method parameters as @MustCallUnknown (top) when the method is called. */
@Override
public void methodFromUsePreSubstitution(ExpressionTree tree, AnnotatedExecutableType type) {
ExecutableElement declaration;
if (tree instanceof MethodInvocationTree) {
declaration = TreeUtils.elementFromUse((MethodInvocationTree) tree);
} else if (tree instanceof MemberReferenceTree) {
declaration = (ExecutableElement) TreeUtils.elementFromUse(tree);
} else {
throw new TypeSystemError("unexpected type of method tree: " + tree.getKind());
}
changeNonOwningParameterTypesToTop(declaration, type);
super.methodFromUsePreSubstitution(tree, type);
}
@Override
protected void constructorFromUsePreSubstitution(
NewClassTree tree, AnnotatedExecutableType type) {
ExecutableElement declaration = TreeUtils.elementFromUse(tree);
changeNonOwningParameterTypesToTop(declaration, type);
super.constructorFromUsePreSubstitution(tree, type);
}
/**
* Changes the type of each parameter not annotated as @Owning to @MustCallUnknown (top). Also
* replaces the component type of the varargs array, if applicable.
*
* This method is not responsible for handling receivers, which can never be owning.
*
* @param declaration a method or constructor declaration
* @param type the method or constructor's type
*/
private void changeNonOwningParameterTypesToTop(
ExecutableElement declaration, AnnotatedExecutableType type) {
List parameterTypes = type.getParameterTypes();
for (int i = 0; i < parameterTypes.size(); i++) {
Element paramDecl = declaration.getParameters().get(i);
if (noLightweightOwnership || getDeclAnnotation(paramDecl, Owning.class) == null) {
AnnotatedTypeMirror paramType = parameterTypes.get(i);
if (!paramType.hasPrimaryAnnotation(POLY)) {
paramType.replaceAnnotation(TOP);
}
}
}
if (declaration.isVarArgs()) {
// also modify the component type of a varargs array
AnnotatedTypeMirror varargsType =
((AnnotatedArrayType) parameterTypes.get(parameterTypes.size() - 1)).getComponentType();
if (!varargsType.hasPrimaryAnnotation(POLY)) {
varargsType.replaceAnnotation(TOP);
}
}
}
@Override
protected DefaultQualifierForUseTypeAnnotator createDefaultForUseTypeAnnotator() {
return new MustCallDefaultQualifierForUseTypeAnnotator();
}
/**
* Returns the {@link MustCall#value} element. For use with {@link
* AnnotationUtils#getElementValueArray}.
*
* @return the {@link MustCall#value} element
*/
public ExecutableElement getMustCallValueElement() {
return mustCallValueElement;
}
/** Support @InheritableMustCall meaning @MustCall on all subtype elements. */
class MustCallDefaultQualifierForUseTypeAnnotator extends DefaultQualifierForUseTypeAnnotator {
/** Creates a {@code MustCallDefaultQualifierForUseTypeAnnotator}. */
public MustCallDefaultQualifierForUseTypeAnnotator() {
super(MustCallAnnotatedTypeFactory.this);
}
@Override
protected AnnotationMirrorSet getExplicitAnnos(Element element) {
AnnotationMirrorSet explict = super.getExplicitAnnos(element);
if (explict.isEmpty() && ElementUtils.isTypeElement(element)) {
AnnotationMirror inheritableMustCall =
getDeclAnnotation(element, InheritableMustCall.class);
if (inheritableMustCall != null) {
List mustCallVal =
AnnotationUtils.getElementValueArray(
inheritableMustCall, inheritableMustCallValueElement, String.class);
return AnnotationMirrorSet.singleton(createMustCall(mustCallVal));
}
}
return explict;
}
}
@Override
protected QualifierUpperBounds createQualifierUpperBounds() {
return new MustCallQualifierUpperBounds();
}
/** Support @InheritableMustCall meaning @MustCall on all subtypes. */
class MustCallQualifierUpperBounds extends QualifierUpperBounds {
/**
* Creates a {@link QualifierUpperBounds} from the MustCall Checker the annotations that are in
* the type hierarchy.
*/
public MustCallQualifierUpperBounds() {
super(MustCallAnnotatedTypeFactory.this);
}
@Override
protected AnnotationMirrorSet getAnnotationFromElement(Element element) {
AnnotationMirrorSet explict = super.getAnnotationFromElement(element);
if (!explict.isEmpty()) {
return explict;
}
AnnotationMirror inheritableMustCall = getDeclAnnotation(element, InheritableMustCall.class);
if (inheritableMustCall != null) {
List mustCallVal =
AnnotationUtils.getElementValueArray(
inheritableMustCall, inheritableMustCallValueElement, String.class);
return AnnotationMirrorSet.singleton(createMustCall(mustCallVal));
}
return AnnotationMirrorSet.emptySet();
}
}
/**
* Cache of the MustCall annotations that have actually been created. Most programs require few
* distinct MustCall annotations (e.g. MustCall() and MustCall("close")).
*/
private final Map, AnnotationMirror> mustCallAnnotations = new HashMap<>(10);
/**
* Creates a {@link MustCall} annotation whose values are the given strings.
*
* @param val the methods that should be called
* @return an annotation indicating that the given methods should be called
*/
public AnnotationMirror createMustCall(List val) {
return mustCallAnnotations.computeIfAbsent(val, this::createMustCallImpl);
}
/**
* Creates a {@link MustCall} annotation whose values are the given strings.
*
* This internal version bypasses the cache, and is only used for new annotations.
*
* @param methodList the methods that should be called
* @return an annotation indicating that the given methods should be called
*/
private AnnotationMirror createMustCallImpl(List methodList) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, MustCall.class);
String[] methodArray = methodList.toArray(new String[methodList.size()]);
Arrays.sort(methodArray);
builder.setValue("value", methodArray);
return builder.build();
}
@Override
protected QualifierHierarchy createQualifierHierarchy() {
return new MustCallQualifierHierarchy(
this.getSupportedTypeQualifiers(), this.getProcessingEnv(), this);
}
/**
* Fetches the store from the results of dataflow for {@code first}. If {@code afterFirstStore} is
* true, then the store after {@code first} is returned; if {@code afterFirstStore} is false, the
* store before {@code succ} is returned.
*
* @param afterFirstStore whether to use the store after the first block or the store before its
* successor, succ
* @param first a block
* @param succ first's successor
* @return the appropriate CFStore, populated with MustCall annotations, from the results of
* running dataflow
*/
public CFStore getStoreForBlock(boolean afterFirstStore, Block first, Block succ) {
return afterFirstStore ? flowResult.getStoreAfter(first) : flowResult.getStoreBefore(succ);
}
/**
* Returns the CreatesMustCallFor.value field/element.
*
* @return the CreatesMustCallFor.value field/element
*/
@Override
public ExecutableElement getCreatesMustCallForValueElement() {
return createsMustCallForValueElement;
}
/**
* Returns the CreatesMustCallFor.List.value field/element.
*
* @return the CreatesMustCallFor.List.value field/element
*/
@Override
public ExecutableElement getCreatesMustCallForListValueElement() {
return createsMustCallForListValueElement;
}
/**
* The TreeAnnotator for the MustCall type system.
*
* This tree annotator treats non-owning method parameters as bottom, regardless of their
* declared type, when they appear in the body of the method. Doing so is safe because being
* non-owning means, by definition, that their must-call obligations are only relevant in the
* callee. (This behavior is disabled if the {@code -AnoLightweightOwnership} option is passed to
* the checker.)
*
*
The tree annotator also changes the type of resource variables to remove "close" from their
* must-call types, because the try-with-resources statement guarantees that close() is called on
* all such variables.
*/
private class MustCallTreeAnnotator extends TreeAnnotator {
/**
* Create a MustCallTreeAnnotator.
*
* @param mustCallAnnotatedTypeFactory the type factory
*/
public MustCallTreeAnnotator(MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory) {
super(mustCallAnnotatedTypeFactory);
}
@Override
public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) {
Element elt = TreeUtils.elementFromUse(tree);
if (elt.getKind() == ElementKind.PARAMETER
&& (noLightweightOwnership || getDeclAnnotation(elt, Owning.class) == null)) {
if (!type.hasPrimaryAnnotation(POLY)) {
// Parameters that are not annotated with @Owning should be treated as bottom
// (to suppress warnings about them). An exception is polymorphic parameters, which
// might be @MustCallAlias (and so wouldn't be annotated with @Owning): these are not
// modified, to support verification of @MustCallAlias annotations.
type.replaceAnnotation(BOTTOM);
}
}
if (ElementUtils.isResourceVariable(elt)) {
type.replaceAnnotation(withoutClose(type.getPrimaryAnnotationInHierarchy(TOP)));
}
return super.visitIdentifier(tree, type);
}
}
/**
* Return the temporary variable for node, if it exists. See {@code #tempVars}.
*
* @param node a CFG node
* @return the corresponding temporary variable, or null if there is not one
*/
public @Nullable LocalVariableNode getTempVar(Node node) {
return tempVars.get(node.getTree());
}
/**
* Returns true if the given type should never have a must-call obligation.
*
* @param type the type to check
* @return true if the given type should never have a must-call obligation
*/
public boolean shouldHaveNoMustCallObligation(TypeMirror type) {
return type.getKind().isPrimitive() || TypesUtils.isClass(type) || TypesUtils.isString(type);
}
/** Qualifier hierarchy for the Must Call Checker. */
class MustCallQualifierHierarchy extends SubtypeIsSubsetQualifierHierarchy {
/**
* Creates a SubtypeIsSubsetQualifierHierarchy from the given classes.
*
* @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
* @param processingEnv processing environment
* @param atypeFactory the associated type factory
*/
public MustCallQualifierHierarchy(
Collection> qualifierClasses,
ProcessingEnvironment processingEnv,
GenericAnnotatedTypeFactory atypeFactory) {
super(qualifierClasses, processingEnv, atypeFactory);
}
@Override
public boolean isSubtypeShallow(
AnnotationMirror subQualifier,
TypeMirror subType,
AnnotationMirror superQualifier,
TypeMirror superType) {
if (shouldHaveNoMustCallObligation(subType) || shouldHaveNoMustCallObligation(superType)) {
return true;
}
return super.isSubtypeShallow(subQualifier, subType, superQualifier, superType);
}
@Override
public @Nullable AnnotationMirror leastUpperBoundShallow(
AnnotationMirror qualifier1, TypeMirror tm1, AnnotationMirror qualifier2, TypeMirror tm2) {
boolean tm1NoMustCall = shouldHaveNoMustCallObligation(tm1);
boolean tm2NoMustCall = shouldHaveNoMustCallObligation(tm2);
if (tm1NoMustCall == tm2NoMustCall) {
return super.leastUpperBoundShallow(qualifier1, tm1, qualifier2, tm2);
} else if (tm1NoMustCall) {
return qualifier1;
} else { // if (tm2NoMustCall) {
return qualifier2;
}
}
}
}