org.checkerframework.checker.resourceleak.ResourceLeakAnnotatedTypeFactory Maven / Gradle / Ivy
Show all versions of checker Show documentation
package org.checkerframework.checker.resourceleak;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.sun.source.tree.Tree;
import java.lang.annotation.Annotation;
import java.util.Arrays;
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.ExecutableElement;
import org.checkerframework.checker.calledmethods.CalledMethodsAnnotatedTypeFactory;
import org.checkerframework.checker.calledmethods.qual.CalledMethods;
import org.checkerframework.checker.calledmethods.qual.CalledMethodsBottom;
import org.checkerframework.checker.calledmethods.qual.CalledMethodsPredicate;
import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods;
import org.checkerframework.checker.mustcall.CreatesMustCallForElementSupplier;
import org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory;
import org.checkerframework.checker.mustcall.MustCallChecker;
import org.checkerframework.checker.mustcall.MustCallNoCreatesMustCallForChecker;
import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor;
import org.checkerframework.checker.mustcall.qual.MustCall;
import org.checkerframework.checker.mustcall.qual.MustCallAlias;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.cfg.ControlFlowGraph;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeSystemError;
/**
* The type factory for the Resource Leak Checker. The main difference between this and the Called
* Methods type factory from which it is derived is that this version's {@link
* #postAnalyze(ControlFlowGraph)} method checks that must-call obligations are fulfilled.
*/
public class ResourceLeakAnnotatedTypeFactory extends CalledMethodsAnnotatedTypeFactory
implements CreatesMustCallForElementSupplier {
/** The MustCall.value element/field. */
final ExecutableElement mustCallValueElement =
TreeUtils.getMethod(MustCall.class, "value", 0, processingEnv);
/** The EnsuresCalledMethods.value element/field. */
final ExecutableElement ensuresCalledMethodsValueElement =
TreeUtils.getMethod(EnsuresCalledMethods.class, "value", 0, processingEnv);
/** The EnsuresCalledMethods.methods element/field. */
final ExecutableElement ensuresCalledMethodsMethodsElement =
TreeUtils.getMethod(EnsuresCalledMethods.class, "methods", 0, processingEnv);
/** The CreatesMustCallFor.List.value element/field. */
private final ExecutableElement createsMustCallForListValueElement =
TreeUtils.getMethod(CreatesMustCallFor.List.class, "value", 0, processingEnv);
/** The CreatesMustCallFor.value element/field. */
private final ExecutableElement createsMustCallForValueElement =
TreeUtils.getMethod(CreatesMustCallFor.class, "value", 0, processingEnv);
/**
* Bidirectional map to store temporary variables created for expressions with non-empty @MustCall
* obligations and the corresponding trees. Keys are the artificial local variable nodes created
* as temporary variables; values are the corresponding trees.
*/
private final BiMap tempVarToTree = HashBiMap.create();
/**
* Creates a new ResourceLeakAnnotatedTypeFactory.
*
* @param checker the checker associated with this type factory
*/
public ResourceLeakAnnotatedTypeFactory(final BaseTypeChecker checker) {
super(checker);
this.postInit();
}
/**
* Is the given element a candidate to be an owning field? A candidate owning field must be final
* and have a non-empty must-call obligation.
*
* @param element a element
* @return true iff the given element is a final field with non-empty @MustCall obligation
*/
boolean isCandidateOwningField(Element element) {
return (element.getKind().isField()
&& ElementUtils.isFinal(element)
&& !getMustCallValue(element).isEmpty());
}
@Override
protected Set> createSupportedTypeQualifiers() {
return getBundledTypeQualifiers(
CalledMethods.class, CalledMethodsBottom.class, CalledMethodsPredicate.class);
}
/**
* Creates a @CalledMethods annotation whose values are the given strings.
*
* @param val the methods that have been called
* @return an annotation indicating that the given methods have been called
*/
public AnnotationMirror createCalledMethods(final String... val) {
return createAccumulatorAnnotation(Arrays.asList(val));
}
@Override
public void postAnalyze(ControlFlowGraph cfg) {
MustCallConsistencyAnalyzer mustCallConsistencyAnalyzer =
new MustCallConsistencyAnalyzer(this, this.analysis);
mustCallConsistencyAnalyzer.analyze(cfg);
// Inferring owning annotations for final owning fields
if (getWholeProgramInference() != null) {
if (cfg.getUnderlyingAST().getKind() == UnderlyingAST.Kind.METHOD) {
MustCallInferenceLogic mustCallInferenceLogic = new MustCallInferenceLogic(this, cfg);
mustCallInferenceLogic.runInference();
}
}
super.postAnalyze(cfg);
tempVarToTree.clear();
}
@Override
protected ResourceLeakAnalysis createFlowAnalysis() {
return new ResourceLeakAnalysis(checker, this);
}
/**
* Returns the {@link MustCall#value} element/argument of the @MustCall annotation on the type of
* {@code tree}.
*
* If possible, prefer {@link #getMustCallValue(Tree)}, which accounts for flow-sensitive
* refinement.
*
* @param tree a tree
* @return the strings in its must-call type
*/
/* package-private */ List getMustCallValue(Tree tree) {
MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory =
getTypeFactoryOfSubchecker(MustCallChecker.class);
AnnotatedTypeMirror mustCallAnnotatedType = mustCallAnnotatedTypeFactory.getAnnotatedType(tree);
AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class);
return getMustCallValues(mustCallAnnotation);
}
/**
* Returns the {@link MustCall#value} element/argument of the @MustCall annotation on the class
* type of {@code element}.
*
* Do not use this method to get the MustCall value of an {@link
* org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation}. Instead, use
* {@link
* org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation#getMustCallMethods(ResourceLeakAnnotatedTypeFactory,
* CFStore)}.
*
* @param element an element
* @return the strings in its must-call type
*/
/* package-private */ List getMustCallValue(Element element) {
MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory =
getTypeFactoryOfSubchecker(MustCallChecker.class);
AnnotatedTypeMirror mustCallAnnotatedType =
mustCallAnnotatedTypeFactory.getAnnotatedType(element);
AnnotationMirror mustCallAnnotation = mustCallAnnotatedType.getAnnotation(MustCall.class);
return getMustCallValues(mustCallAnnotation);
}
/**
* Helper method for getting the must-call values from a must-call annotation.
*
* @param mustCallAnnotation a {@link MustCall} annotation, or null
* @return the strings in mustCallAnnotation's value element, or the empty list if
* mustCallAnnotation is null
*/
/* package-private */ List getMustCallValues(
@Nullable AnnotationMirror mustCallAnnotation) {
if (mustCallAnnotation == null) {
return Collections.emptyList();
}
return AnnotationUtils.getElementValueArray(
mustCallAnnotation, mustCallValueElement, String.class);
}
/**
* Helper method to get the temporary variable that represents the given node, if one exists.
*
* @param node a node
* @return the tempvar for node's expression, or null if one does not exist
*/
/* package-private */
@Nullable LocalVariableNode getTempVarForNode(Node node) {
return tempVarToTree.inverse().get(node.getTree());
}
/**
* Is the given node a temporary variable?
*
* @param node a node
* @return true iff the given node is a temporary variable
*/
/* package-private */ boolean isTempVar(Node node) {
return tempVarToTree.containsKey(node);
}
/**
* Gets the tree for a temporary variable
*
* @param node a node for a temporary variable
* @return the tree for {@code node}
*/
/* package-private */ Tree getTreeForTempVar(Node node) {
if (!tempVarToTree.containsKey(node)) {
throw new TypeSystemError(node + " must be a temporary variable");
}
return tempVarToTree.get(node);
}
/**
* Registers a temporary variable by adding it to this type factory's tempvar map.
*
* @param tmpVar a temporary variable
* @param tree the tree of the expression the tempvar represents
*/
/* package-private */ void addTempVar(LocalVariableNode tmpVar, Tree tree) {
tempVarToTree.put(tmpVar, tree);
}
/**
* Returns true if the type of the tree includes a must-call annotation. Note that this method may
* not consider dataflow, and is only safe to use when you need the declared, rather than
* inferred, type of the tree.
*
* Do not use this method if you are trying to get the must-call obligations of the resource
* aliases of an {@link
* org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation}. Instead, use
* {@link
* org.checkerframework.checker.resourceleak.MustCallConsistencyAnalyzer.Obligation#getMustCallMethods(ResourceLeakAnnotatedTypeFactory,
* CFStore)}.
*
* @param tree a tree
* @return whether the tree has declared must-call obligations
*/
/* package-private */ boolean declaredTypeHasMustCall(Tree tree) {
assert tree.getKind() == Tree.Kind.METHOD
|| tree.getKind() == Tree.Kind.VARIABLE
|| tree.getKind() == Tree.Kind.NEW_CLASS
|| tree.getKind() == Tree.Kind.METHOD_INVOCATION
: "unexpected declaration tree kind: " + tree.getKind();
return !getMustCallValue(tree).isEmpty();
}
/**
* Returns true if the given tree has an {@link MustCallAlias} annotation and resource-alias
* tracking is not disabled.
*
* @param tree a tree
* @return true if the given tree has an {@link MustCallAlias} annotation
*/
/* package-private */ boolean hasMustCallAlias(Tree tree) {
Element elt = TreeUtils.elementFromTree(tree);
return hasMustCallAlias(elt);
}
/**
* Returns true if the given element has an {@link MustCallAlias} annotation and resource-alias
* tracking is not disabled.
*
* @param elt an element
* @return true if the given element has an {@link MustCallAlias} annotation
*/
/* package-private */ boolean hasMustCallAlias(Element elt) {
if (checker.hasOption(MustCallChecker.NO_RESOURCE_ALIASES)) {
return false;
}
MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory =
getTypeFactoryOfSubchecker(MustCallChecker.class);
return mustCallAnnotatedTypeFactory.getDeclAnnotationNoAliases(elt, MustCallAlias.class)
!= null;
}
/**
* Returns true if the declaration of the method being invoked has one or more {@link
* CreatesMustCallFor} annotations.
*
* @param node a method invocation node
* @return true iff there is one or more @CreatesMustCallFor annotations on the declaration of the
* invoked method
*/
public boolean hasCreatesMustCallFor(MethodInvocationNode node) {
ExecutableElement decl = TreeUtils.elementFromUse(node.getTree());
return getDeclAnnotation(decl, CreatesMustCallFor.class) != null
|| getDeclAnnotation(decl, CreatesMustCallFor.List.class) != null;
}
/**
* Does this type factory support {@link CreatesMustCallFor}?
*
* @return true iff the -AnoCreatesMustCallFor command-line argument was not supplied to the
* checker
*/
public boolean canCreateObligations() {
return !checker.hasOption(MustCallChecker.NO_CREATES_MUSTCALLFOR);
}
@Override
@SuppressWarnings("TypeParameterUnusedInFormals") // Intentional abuse
public > @Nullable T getTypeFactoryOfSubchecker(
Class subCheckerClass) {
if (subCheckerClass == MustCallChecker.class) {
if (!canCreateObligations()) {
return super.getTypeFactoryOfSubchecker(MustCallNoCreatesMustCallForChecker.class);
}
}
return super.getTypeFactoryOfSubchecker(subCheckerClass);
}
/**
* Returns the {@link CreatesMustCallFor#value} element.
*
* @return the {@link CreatesMustCallFor#value} element
*/
@Override
public ExecutableElement getCreatesMustCallForValueElement() {
return createsMustCallForValueElement;
}
/**
* Returns the {@link org.checkerframework.checker.mustcall.qual.CreatesMustCallFor.List#value}
* element.
*
* @return the {@link org.checkerframework.checker.mustcall.qual.CreatesMustCallFor.List#value}
* element
*/
@Override
public ExecutableElement getCreatesMustCallForListValueElement() {
return createsMustCallForListValueElement;
}
}