All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.checker.resourceleak.ResourceLeakVisitor Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.44.0
Show newest version
package org.checkerframework.checker.resourceleak;

import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
import java.util.ArrayList;
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 javax.lang.model.element.TypeElement;
import org.checkerframework.checker.calledmethods.CalledMethodsVisitor;
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.qual.CreatesMustCallFor;
import org.checkerframework.checker.mustcall.qual.Owning;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.flow.CFAbstractStore;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;

/**
 * The visitor for the Resource Leak Checker. Responsible for checking that the rules for {@link
 * Owning} fields are satisfied, and for checking that {@link CreatesMustCallFor} overrides are
 * valid.
 */
public class ResourceLeakVisitor extends CalledMethodsVisitor {

  /**
   * Because CalledMethodsVisitor doesn't have a type parameter, we need a reference to the type
   * factory that has this static type to access the features that ResourceLeakAnnotatedTypeFactory
   * implements but CalledMethodsAnnotatedTypeFactory does not.
   */
  private final ResourceLeakAnnotatedTypeFactory rlTypeFactory;

  /**
   * Create the visitor.
   *
   * @param checker the type-checker associated with this visitor
   */
  public ResourceLeakVisitor(final BaseTypeChecker checker) {
    super(checker);
    rlTypeFactory = (ResourceLeakAnnotatedTypeFactory) atypeFactory;
  }

  @Override
  protected ResourceLeakAnnotatedTypeFactory createTypeFactory() {
    return new ResourceLeakAnnotatedTypeFactory(checker);
  }

  @Override
  public Void visitMethod(MethodTree node, Void p) {
    ExecutableElement elt = TreeUtils.elementFromDeclaration(node);
    MustCallAnnotatedTypeFactory mcAtf =
        rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class);
    List cmcfValues = getCreatesMustCallForValues(elt, mcAtf, rlTypeFactory);
    if (!cmcfValues.isEmpty()) {
      // If this method overrides another method, it must create at least as many
      // obligations. Without this check, dynamic dispatch might allow e.g. a field to be
      // overwritten by a CMCF method, but the CMCF effect wouldn't occur.
      for (ExecutableElement overridden : ElementUtils.getOverriddenMethods(elt, this.types)) {
        List overriddenCmcfValues =
            getCreatesMustCallForValues(overridden, mcAtf, rlTypeFactory);
        if (!overriddenCmcfValues.containsAll(cmcfValues)) {
          String foundCmcfValueString = String.join(", ", cmcfValues);
          String neededCmcfValueString = String.join(", ", overriddenCmcfValues);
          String actualClassname = ElementUtils.getEnclosingClassName(elt);
          String overriddenClassname = ElementUtils.getEnclosingClassName(overridden);
          checker.reportError(
              node,
              "creates.mustcall.for.override.invalid",
              actualClassname + "#" + elt,
              overriddenClassname + "#" + overridden,
              foundCmcfValueString,
              neededCmcfValueString);
        }
      }
    }
    return super.visitMethod(node, p);
  }

  // Overwritten to check that destructors (i.e. methods responsible for resolving
  // the must-call obligations of owning fields) enforce a stronger version of
  // @EnsuresCalledMethods: that the claimed @CalledMethods annotation is true on
  // both exceptional and regular exits, not just on regular exits.
  @Override
  protected void checkPostcondition(
      MethodTree methodTree, AnnotationMirror annotation, JavaExpression expression) {
    super.checkPostcondition(methodTree, annotation, expression);
    // Only check if the required annotation is a CalledMethods annotation (implying
    // the method was annotated with @EnsuresCalledMethods).
    if (!AnnotationUtils.areSameByName(
        annotation, "org.checkerframework.checker.calledmethods.qual.CalledMethods")) {
      return;
    }
    if (!isMustCallMethod(methodTree)) {
      // In this case, the method has an EnsuresCalledMethods annotation but is not a destructor,
      // so no further checking is required.
      return;
    }
    CFAbstractStore exitStore = atypeFactory.getExceptionalExitStore(methodTree);
    if (exitStore == null) {
      // If there is no exceptional exitStore, then the method cannot throw an exception and there
      // is no need to check anything else.
    } else {
      CFAbstractValue value = exitStore.getValue(expression);
      AnnotationMirror inferredAnno = null;
      if (value != null) {
        QualifierHierarchy hierarchy = atypeFactory.getQualifierHierarchy();
        Set annos = value.getAnnotations();
        inferredAnno = hierarchy.findAnnotationInSameHierarchy(annos, annotation);
      }
      if (!checkContract(expression, annotation, inferredAnno, exitStore)) {
        String inferredAnnoStr =
            inferredAnno == null
                ? "no information about " + expression.toString()
                : inferredAnno.toString();
        checker.reportError(
            methodTree,
            "destructor.exceptional.postcondition",
            methodTree.getName(),
            expression.toString(),
            inferredAnnoStr,
            annotation);
      }
    }
  }

  /**
   * Returns true iff the {@code MustCall} annotation of the class that encloses the methodTree
   * names this method.
   *
   * @param methodTree the declaration of a method
   * @return whether that method is one of the must-call methods for its enclosing class
   */
  private boolean isMustCallMethod(MethodTree methodTree) {
    ExecutableElement elt = TreeUtils.elementFromDeclaration(methodTree);
    TypeElement containingClass = ElementUtils.enclosingTypeElement(elt);
    MustCallAnnotatedTypeFactory mustCallAnnotatedTypeFactory =
        rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class);
    AnnotationMirror mcAnno =
        mustCallAnnotatedTypeFactory
            .getAnnotatedType(containingClass)
            .getAnnotationInHierarchy(mustCallAnnotatedTypeFactory.TOP);
    List mcValues =
        AnnotationUtils.getElementValueArray(
            mcAnno, mustCallAnnotatedTypeFactory.getMustCallValueElement(), String.class);
    String methodName = elt.getSimpleName().toString();
    return mcValues.contains(methodName);
  }

  /**
   * Returns the {@link CreatesMustCallFor#value} element/argument of the given @CreatesMustCallFor
   * annotation, or "this" if there is none.
   *
   * 

Does not vipewpoint-adaptation. * * @param createsMustCallFor an @CreatesMustCallFor annotation * @param mcAtf a MustCallAnnotatedTypeFactory, to source the value element * @return the string value */ private static String getCreatesMustCallForValue( AnnotationMirror createsMustCallFor, MustCallAnnotatedTypeFactory mcAtf) { return AnnotationUtils.getElementValue( createsMustCallFor, mcAtf.getCreatesMustCallForValueElement(), String.class, "this"); } /** * Returns all the {@link CreatesMustCallFor#value} elements/arguments of all @CreatesMustCallFor * annotations on the given element. * *

Does no viewpoint-adaptation, unlike {@link * CreatesMustCallForElementSupplier#getCreatesMustCallForExpressions} which does. * * @param elt an executable element * @param mcAtf a MustCallAnnotatedTypeFactory, to source the value element * @param atypeFactory a ResourceLeakAnnotatedTypeFactory * @return the literal strings present in the @CreatesMustCallFor annotation(s) of that element, * substituting the default "this" for empty annotations. This method returns the empty list * iff there are no @CreatesMustCallFor annotations on elt. The returned list is always * modifiable if it is non-empty. */ /*package-private*/ static List getCreatesMustCallForValues( ExecutableElement elt, MustCallAnnotatedTypeFactory mcAtf, ResourceLeakAnnotatedTypeFactory atypeFactory) { AnnotationMirror createsMustCallForList = atypeFactory.getDeclAnnotation(elt, CreatesMustCallFor.List.class); List result = new ArrayList<>(4); if (createsMustCallForList != null) { List createsMustCallFors = AnnotationUtils.getElementValueArray( createsMustCallForList, mcAtf.getCreatesMustCallForListValueElement(), AnnotationMirror.class); for (AnnotationMirror cmcf : createsMustCallFors) { result.add(getCreatesMustCallForValue(cmcf, mcAtf)); } } AnnotationMirror createsMustCallFor = atypeFactory.getDeclAnnotation(elt, CreatesMustCallFor.class); if (createsMustCallFor != null) { result.add(getCreatesMustCallForValue(createsMustCallFor, mcAtf)); } return result; } @Override public Void visitVariable(VariableTree node, Void p) { Element varElement = TreeUtils.elementFromTree(node); if (varElement.getKind().isField() && !checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP) && rlTypeFactory.getDeclAnnotation(varElement, Owning.class) != null) { checkOwningField(varElement); } return super.visitVariable(node, p); } /** * Checks validity of a field {@code field} with an {@code @}{@link Owning} annotation. Say the * type of {@code field} is {@code @MustCall("m"}}. This method checks that the enclosing class of * {@code field} has a type {@code @MustCall("m2")} for some method {@code m2}, and that {@code * m2} has an annotation {@code @EnsuresCalledMethods(value = "this.field", methods = "m")}, * guaranteeing that the {@code @MustCall} obligation of the field will be satisfied. * * @param field the declaration of the field to check */ private void checkOwningField(Element field) { if (checker.shouldSkipUses(field)) { return; } // This value is side-effected. List unsatisfiedMustCallObligationsOfOwningField = rlTypeFactory.getMustCallValue(field); if (unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { return; } String error = ""; Element enclosingElement = field.getEnclosingElement(); List enclosingMustCallValues = rlTypeFactory.getMustCallValue(enclosingElement); if (enclosingMustCallValues == null) { error = " The enclosing element " + ElementUtils.getQualifiedName(enclosingElement) + " doesn't have a @MustCall annotation"; } else { List siblingsOfOwningField = enclosingElement.getEnclosedElements(); for (Element siblingElement : siblingsOfOwningField) { if (siblingElement.getKind() == ElementKind.METHOD && enclosingMustCallValues.contains(siblingElement.getSimpleName().toString())) { AnnotationMirror ensuresCalledMethodsAnno = rlTypeFactory.getDeclAnnotation(siblingElement, EnsuresCalledMethods.class); if (ensuresCalledMethodsAnno != null) { List values = AnnotationUtils.getElementValueArray( ensuresCalledMethodsAnno, rlTypeFactory.ensuresCalledMethodsValueElement, String.class); for (String value : values) { if (value.contains(field.getSimpleName().toString())) { List methods = AnnotationUtils.getElementValueArray( ensuresCalledMethodsAnno, rlTypeFactory.ensuresCalledMethodsMethodsElement, String.class); unsatisfiedMustCallObligationsOfOwningField.removeAll(methods); } } if (unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { return; } } if (!unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { // This variable could be set immediately before reporting the error, but IMO // it is more clear to set it here. error = " @EnsuresCalledMethods written on MustCall methods doesn't contain " + MustCallConsistencyAnalyzer.formatMissingMustCallMethods( unsatisfiedMustCallObligationsOfOwningField); } } } } if (!unsatisfiedMustCallObligationsOfOwningField.isEmpty()) { checker.reportError( field, "required.method.not.called", MustCallConsistencyAnalyzer.formatMissingMustCallMethods( unsatisfiedMustCallObligationsOfOwningField), "field " + field.getSimpleName().toString(), field.asType().toString(), error); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy