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

org.checkerframework.checker.mustcall.MustCallVisitor 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.43.0
Show newest version
package org.checkerframework.checker.mustcall;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
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.ArrayList;
import java.util.Collections;
import java.util.List;
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 javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
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.NotOwning;
import org.checkerframework.checker.mustcall.qual.Owning;
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.AnnotationMirrorSet;
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 {

  /** True if -AnoLightweightOwnership was passed on the command line. */
  private final boolean noLightweightOwnership;

  /**
   * Creates a new MustCallVisitor.
   *
   * @param checker the type-checker associated with this visitor
   */
  public MustCallVisitor(BaseTypeChecker checker) {
    super(checker);
    noLightweightOwnership = checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP);
  }

  @Override
  public Void visitReturn(ReturnTree tree, Void p) {
    // Only check return types if ownership is being transferred.
    if (!noLightweightOwnership) {
      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(tree, p);
  }

  @Override
  public Void visitAssignment(AssignmentTree tree, Void p) {
    // This code implements the following rule:
    //  * It is always safe to assign a MustCallAlias parameter of a constructor
    //    to an owning field of the containing class.
    // It is necessary to special case this because MustCallAlias is translated
    // into @PolyMustCall, so the common assignment check will fail when assigning
    // an @MustCallAlias parameter to an owning field: the parameter is polymorphic,
    // but the field is not.
    ExpressionTree lhs = tree.getVariable();
    ExpressionTree rhs = tree.getExpression();
    Element lhsElt = TreeUtils.elementFromTree(lhs);
    Element rhsElt = TreeUtils.elementFromTree(rhs);
    if (lhsElt != null && rhsElt != null) {
      // Note that it is not necessary to check that the assignment is to a field of this, because
      // that is implied by the other conditions:
      // * if the field is final, then the only place it can be assigned to is in the constructor
      //   of the proper object (enforced by javac).
      // * if the field is not final, then it cannot be assigned to in a constructor at all: the
      //   @CreatesMustCallFor annotation cannot be written on a constructor (it has
      //   @Target({ElementType.METHOD})), so this code relies on the standard rules for non-final
      //   owning field reassignment, which prevent it without an @CreatesMustCallFor annotation
      //   except in the constructor of the object containing the field.
      boolean lhsIsOwningField =
          lhs.getKind() == Tree.Kind.MEMBER_SELECT
              && atypeFactory.getDeclAnnotation(lhsElt, Owning.class) != null;
      boolean rhsIsMCA =
          AnnotationUtils.containsSameByClass(rhsElt.getAnnotationMirrors(), MustCallAlias.class);
      boolean rhsIsConstructorParam =
          rhsElt.getKind() == ElementKind.PARAMETER
              && rhsElt.getEnclosingElement().getKind() == ElementKind.CONSTRUCTOR;
      if (lhsIsOwningField && rhsIsMCA && rhsIsConstructorParam) {
        // Do not execute common assignment check.
        return null;
      }
    }

    return super.visitAssignment(tree, p);
  }

  /** An empty string list. */
  private static final List emptyStringList = Collections.emptyList();

  @Override
  protected boolean validateType(Tree tree, AnnotatedTypeMirror type) {
    if (TreeUtils.isClassTree(tree)) {
      TypeElement classEle = TreeUtils.elementFromDeclaration((ClassTree) tree);
      // If no @InheritableMustCall annotation is written here, `getDeclAnnotation()` gets one
      // from stub files and supertypes.
      AnnotationMirror anyInheritableMustCall =
          atypeFactory.getDeclAnnotation(classEle, InheritableMustCall.class);
      // An @InheritableMustCall annotation that is directly present.
      AnnotationMirror directInheritableMustCall =
          AnnotationUtils.getAnnotationByClass(
              classEle.getAnnotationMirrors(), InheritableMustCall.class);
      if (anyInheritableMustCall == null) {
        if (!ElementUtils.isFinal(classEle)) {
          // There is no @InheritableMustCall annotation on this or any superclass and
          // this is a non-final class.
          // If an explicit @MustCall annotation is present, issue a warning suggesting
          // that @InheritableMustCall is probably what the programmer means, for
          // usability.
          if (atypeFactory.getDeclAnnotation(classEle, MustCall.class) != null) {
            checker.reportWarning(
                tree, "mustcall.not.inheritable", ElementUtils.getQualifiedName(classEle));
          }
        }
      } else {
        // There is an @InheritableMustCall annotation on this, on a superclass, or in an
        // annotation file.
        // There are two possible problems:
        //  1. There is an inconsistent @MustCall on this.
        //  2. There is an explicit @InheritableMustCall here, and it is inconsistent with
        //     an @InheritableMustCall annotation on a supertype.

        // Check for problem 1.
        AnnotationMirror explicitMustCall =
            atypeFactory.fromElement(classEle).getPrimaryAnnotation();
        if (explicitMustCall != null) {
          // There is a @MustCall annotation here.

          List inheritableMustCallVal =
              AnnotationUtils.getElementValueArray(
                  anyInheritableMustCall,
                  atypeFactory.inheritableMustCallValueElement,
                  String.class,
                  emptyStringList);
          AnnotationMirror inheritedMCAnno = atypeFactory.createMustCall(inheritableMustCallVal);

          // Issue an error if there is an inconsistent, user-written @MustCall annotation
          // here.
          AnnotationMirror effectiveMCAnno = type.getPrimaryAnnotation();
          TypeMirror tm = type.getUnderlyingType();
          if (effectiveMCAnno != null
              && !qualHierarchy.isSubtypeShallow(inheritedMCAnno, effectiveMCAnno, tm)) {

            checker.reportError(
                tree,
                "inconsistent.mustcall.subtype",
                ElementUtils.getQualifiedName(classEle),
                effectiveMCAnno,
                anyInheritableMustCall);
            return false;
          }
        }

        // Check for problem 2.
        if (directInheritableMustCall != null) {

          // `inheritedImcs` is inherited @InheritableMustCall annotations.
          List inheritedImcs = new ArrayList<>();
          for (TypeElement elt : ElementUtils.getDirectSuperTypeElements(classEle, elements)) {
            AnnotationMirror imc = atypeFactory.getDeclAnnotation(elt, InheritableMustCall.class);
            if (imc != null) {
              inheritedImcs.add(imc);
            }
          }
          if (!inheritedImcs.isEmpty()) {
            // There is an inherited @InheritableMustCall annotation, in addition to the
            // one written explicitly here.
            List inheritedMustCallVal = new ArrayList<>();
            for (AnnotationMirror inheritedImc : inheritedImcs) {
              inheritedMustCallVal.addAll(
                  AnnotationUtils.getElementValueArray(
                      inheritedImc, atypeFactory.inheritableMustCallValueElement, String.class));
            }
            AnnotationMirror inheritedMCAnno = atypeFactory.createMustCall(inheritedMustCallVal);

            AnnotationMirror effectiveMCAnno = type.getPrimaryAnnotation();

            TypeMirror tm = type.getUnderlyingType();

            if (!qualHierarchy.isSubtypeShallow(inheritedMCAnno, effectiveMCAnno, tm)) {

              checker.reportError(
                  tree,
                  "inconsistent.mustcall.subtype",
                  ElementUtils.getQualifiedName(classEle),
                  effectiveMCAnno,
                  inheritedMCAnno);
              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, despite not technically
    // being a part of the type hierarchy. It's necessary to get the annotation from the element
    // because MustCallAlias is aliased to PolyMustCall, which is what useType would contain.
    // Note that isValidUse does not need to consider component types, on which it should be
    // called separately.
    Element elt = TreeUtils.elementFromTree(tree);
    if (elt != null
        && AnnotationUtils.containsSameByClass(elt.getAnnotationMirrors(), MustCallAlias.class)) {
      return true;
    }
    return super.isValidUse(declarationType, useType, tree);
  }

  @Override
  protected boolean skipReceiverSubtypeCheck(
      MethodInvocationTree tree,
      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.
   *
   * 

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; /** * {@inheritDoc} * *

Mark (using the {@code #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 boolean commonAssignmentCheck( Tree varTree, ExpressionTree valueExp, @CompilerMessageKey String errorKey, Object... extraArgs) { Element elt = TreeUtils.elementFromTree(varTree); if (elt != null && elt.getKind() == ElementKind.RESOURCE_VARIABLE) { commonAssignmentCheckOnResourceVariable = true; } return super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); } /** * {@inheritDoc} * *

Iff the LHS is a resource variable, then {@code #commonAssignmentCheckOnResourceVariable} * will be true. This method guarantees that {@code #commonAssignmentCheckOnResourceVariable} will * be false when it returns. */ @Override protected boolean 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.getPrimaryAnnotationInHierarchy(atypeFactory.TOP); AnnotationMirror valueAnno = valueType.getPrimaryAnnotationInHierarchy(atypeFactory.TOP); if (qualHierarchy.isSubtypeShallow( atypeFactory.withoutClose(valueAnno), valueType.getUnderlyingType(), atypeFactory.withoutClose(varAnno), varType.getUnderlyingType())) { return true; } // 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. return 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. * *

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 an 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.getPrimaryAnnotationInHierarchy(atypeFactory.TOP); AnnotatedTypeMirror resultType = constructorType.getReturnType(); AnnotationMirror resultAnno = resultType.getPrimaryAnnotationInHierarchy(atypeFactory.TOP); if (!qualHierarchy.isSubtypeShallow( defaultAnno, defaultType.getUnderlyingType(), resultAnno, resultType.getUnderlyingType())) { 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. * *

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 AnnotationMirrorSet getExceptionParameterLowerBoundAnnotations() { return new AnnotationMirrorSet(atypeFactory.BOTTOM); } /** * Does not issue any warnings. * *

This implementation prevents recursing into annotation arguments. Annotation arguments are * literals, which don't have must-call obligations. * *

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 tree, Void p) { return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy