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

org.checkerframework.common.aliasing.AliasingVisitor 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.42.0-eisop4
Show newest version
package org.checkerframework.common.aliasing;

import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;

import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.formatter.qual.FormatMethod;
import org.checkerframework.common.aliasing.qual.LeakedToResult;
import org.checkerframework.common.aliasing.qual.NonLeaked;
import org.checkerframework.common.aliasing.qual.Unique;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.javacutil.AnnotationMirrorSet;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;

import java.util.List;

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;

/**
 * This visitor ensures that every constructor whose result is annotated as {@literal @}Unique does
 * not leak aliases.
 *
 * 

TODO: Implement {@literal @}NonLeaked and {@literal @}LeakedToResult verifications: * *

{@literal @}NonLeaked: When a method declaration has a parameter annotated as * {@literal @}NonLeaked, the method body must not leak a reference to that parameter. * *

{@literal @}LeakedToResult: When a method declaration has a parameter annotated as * {@literal @}LeakedToResult, the method body must not leak a reference to that parameter, except * at the method return statements. * *

Both of the checks above are similar to the @Unique check that is implemented in this visitor. */ public class AliasingVisitor extends BaseTypeVisitor { public AliasingVisitor(BaseTypeChecker checker) { super(checker); } /** * Checks that if a method call is being invoked inside a constructor with result type * {@literal @}Unique, it must not leak the "this" reference. There are 3 ways to make sure that * this is not happening: * *

    *
  1. {@code this} is not an argument of the method call. *
  2. {@code this} is an argument of the method call, but the respective parameter is * annotated as {@literal @}NonLeaked. *
  3. {@code this} is an argument of the method call, but the respective parameter is * annotated as {@literal @}LeakedToResult AND the result of the method call is not being * stored (the method call is a statement). *
* * The private method {@code isUniqueCheck} handles cases 2 and 3. */ @Override public Void visitMethodInvocation(MethodInvocationTree tree, Void p) { // The check only needs to be done for constructors with result type // @Unique. We also want to avoid visiting the method. if (isInUniqueConstructor()) { if (TreeUtils.isSuperConstructorCall(tree)) { // Check if a call to super() might create an alias: that // happens when the parent's respective constructor is not @Unique. AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(tree); if (!superResult.hasAnnotation(Unique.class)) { checker.reportError(tree, "unique.leaked"); } } else { // TODO: Currently the type of "this" doesn't always return the type of the // constructor result, therefore we need this "else" block. Once constructors are // implemented correctly we could remove that code below, since the type of "this" // in a @Unique constructor will be @Unique. Tree parent = getCurrentPath().getParentPath().getLeaf(); boolean parentIsStatement = parent.getKind() == Tree.Kind.EXPRESSION_STATEMENT; ExecutableElement methodElement = TreeUtils.elementFromUse(tree); List params = methodElement.getParameters(); List args = tree.getArguments(); assert (args.size() == params.size()) : "Number of arguments in" + " the method call " + tree + " is different from the " + "number of parameters for the method declaration: " + methodElement.getSimpleName(); for (int i = 0; i < args.size(); i++) { // Here we are traversing the arguments of the method call. // For every argument we check if it is a reference to "this". if (TreeUtils.isExplicitThisDereference(args.get(i))) { // If it is a reference to "this", there is still hope that // it is not being leaked (2. and 3. from the javadoc). VariableElement param = params.get(i); boolean hasNonLeaked = atypeFactory.getAnnotatedType(param).hasAnnotation(NonLeaked.class); boolean hasLeakedToResult = atypeFactory .getAnnotatedType(param) .hasAnnotation(LeakedToResult.class); isUniqueCheck(tree, parentIsStatement, hasNonLeaked, hasLeakedToResult); } else { // Not possible to leak reference here (case 1. from the javadoc). } } // Now, doing the same as above for the receiver parameter AnnotatedExecutableType annotatedType = atypeFactory.getAnnotatedType(methodElement); AnnotatedDeclaredType receiverType = annotatedType.getReceiverType(); if (receiverType != null) { boolean hasNonLeaked = receiverType.hasAnnotation(NonLeaked.class); boolean hasLeakedToResult = receiverType.hasAnnotation(LeakedToResult.class); isUniqueCheck(tree, parentIsStatement, hasNonLeaked, hasLeakedToResult); } } } return super.visitMethodInvocation(tree, p); } /** * Possibly issue a "unique.leaked" error. * * @param tree a method invocation * @param parentIsStatement is the parent of {@code tree} a statement? * @param hasNonLeaked does the receiver have a {@code @NonLeaked} annotation? * @param hasLeakedToResult does the receiver have a {@code @LeakedToResult} annotation? */ private void isUniqueCheck( MethodInvocationTree tree, boolean parentIsStatement, boolean hasNonLeaked, boolean hasLeakedToResult) { if (hasNonLeaked || (hasLeakedToResult && parentIsStatement)) { // Not leaked according to cases 2. and 3. from the javadoc of // visitMethodInvocation. } else { // May be leaked, raise warning. checker.reportError(tree, "unique.leaked"); } } // TODO: Merge that code in commonAssignmentCheck(AnnotatedTypeMirror varType, ExpressionTree // valueExp, String errorKey, boolean isLocalVariableAssignment), because the method below // isn't called for pseudo-assignments, but the mentioned one is. The issue of copy-pasting the // code from this method to the other one is that a declaration such as: List<@Unique Object> // will raise a unique.leaked warning, as there is a pseudo-assignment from @Unique to a // @MaybeAliased object, if the @Unique annotation is not in the stubfile. TODO: Change the // documentation in BaseTypeVisitor to point out that this isn't called for pseudo-assignments. @Override protected boolean commonAssignmentCheck( Tree varTree, ExpressionTree valueExp, @CompilerMessageKey String errorKey, Object... extraArgs) { boolean result = super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs); if (isInUniqueConstructor() && TreeUtils.isExplicitThisDereference(valueExp)) { // If an assignment occurs inside a constructor with result type @Unique, it will // invalidate the @Unique property by using the "this" reference. checker.reportError(valueExp, "unique.leaked"); result = false; } else if (canBeLeaked(valueExp)) { checker.reportError(valueExp, "unique.leaked"); result = false; } return result; } @Override @FormatMethod protected boolean commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, @CompilerMessageKey String errorKey, Object... extraArgs) { boolean result = super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); // If we are visiting a pseudo-assignment, visitorLeafKind is either // Tree.Kind.NEW_CLASS or Tree.Kind.METHOD_INVOCATION. TreePath path = getCurrentPath(); if (path == null) { return result; } Tree.Kind visitorLeafKind = path.getLeaf().getKind(); if (visitorLeafKind == Tree.Kind.NEW_CLASS || visitorLeafKind == Tree.Kind.METHOD_INVOCATION) { // Handling pseudo-assignments if (canBeLeaked(valueTree)) { Tree.Kind parentKind = getCurrentPath().getParentPath().getLeaf().getKind(); if (!varType.hasAnnotation(NonLeaked.class) && !(varType.hasAnnotation(LeakedToResult.class) && parentKind == Tree.Kind.EXPRESSION_STATEMENT)) { checker.reportError(valueTree, "unique.leaked"); result = false; } } } return result; } @Override public Void visitThrow(ThrowTree tree, Void p) { // throw is also an escape mechanism. If an expression of type // @Unique is thrown, it is not @Unique anymore. ExpressionTree exp = tree.getExpression(); if (canBeLeaked(exp)) { checker.reportError(exp, "unique.leaked"); } return super.visitThrow(tree, p); } @Override public Void visitVariable(VariableTree tree, Void p) { // Component types are not allowed to have the @Unique annotation. AnnotatedTypeMirror varType = atypeFactory.getAnnotatedType(tree); VariableElement elt = TreeUtils.elementFromDeclaration(tree); if (elt.getKind().isField() && varType.hasExplicitAnnotation(Unique.class)) { checker.reportError(tree, "unique.location.forbidden"); } else if (tree.getType() != null) { // VariableTree#getType returns null for binding variables from a // DeconstructionPatternTree. if (tree.getType().getKind() == Tree.Kind.ARRAY_TYPE) { AnnotatedArrayType arrayType = (AnnotatedArrayType) varType; if (arrayType.getComponentType().hasAnnotation(Unique.class)) { checker.reportError(tree, "unique.location.forbidden"); } } else if (tree.getType().getKind() == Tree.Kind.PARAMETERIZED_TYPE) { AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) varType; for (AnnotatedTypeMirror atm : declaredType.getTypeArguments()) { if (atm.hasAnnotation(Unique.class)) { checker.reportError(tree, "unique.location.forbidden"); } } } } return super.visitVariable(tree, p); } @Override public Void visitNewArray(NewArrayTree tree, Void p) { List initializers = tree.getInitializers(); if (initializers != null && !initializers.isEmpty()) { for (ExpressionTree exp : initializers) { if (canBeLeaked(exp)) { checker.reportError(exp, "unique.leaked"); } } } return super.visitNewArray(tree, p); } @Override protected void checkConstructorResult( AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { // @Unique is verified, so don't check this. AnnotatedTypeMirror returnType = constructorType.getReturnType(); if (returnType.hasAnnotation(atypeFactory.UNIQUE)) { return; } // Don't issue warnings about @LeakedToResult or (implicit) @MaybeLeaked on constructor // results. if (!returnType.hasAnnotation(atypeFactory.NON_LEAKED)) { // TODO: the visitor should not change qualifiers. // Possible problem from aliasing of `returnType`, but all tests pass. returnType.replaceAnnotation(atypeFactory.NON_LEAKED); } super.checkConstructorResult(constructorType, constructorElement); } @Override protected void checkThisOrSuperConstructorCall( MethodInvocationTree superCall, @CompilerMessageKey String errorKey) { if (isInUniqueConstructor()) { // Check if a call to super() might create an alias: that // happens when the parent's respective constructor is not @Unique. AnnotatedTypeMirror superResult = atypeFactory.getAnnotatedType(superCall); if (!superResult.hasAnnotation(Unique.class)) { checker.reportError(superCall, "unique.leaked"); } } } /** * Returns true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new * class expression. It checks whether the tree expression is unique by either checking for an * explicit annotation or checking whether the class of the tree expression {@code exp} has type * {@code @Unique} * * @param exp the Tree to check * @return true if {@code exp} has type {@code @Unique} and is not a method invocation nor a new * class expression */ private boolean canBeLeaked(Tree exp) { AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(exp); boolean isMethodInvocation = exp.getKind() == Tree.Kind.METHOD_INVOCATION; boolean isNewClass = exp.getKind() == Tree.Kind.NEW_CLASS; boolean isUniqueType = isUniqueClass(type) || type.hasExplicitAnnotation(Unique.class); return isUniqueType && !isMethodInvocation && !isNewClass; } /** * Return true if the class declaration for annotated type {@code type} has annotation * {@code @Unique}. * * @param type the annotated type whose class must be checked * @return boolean true if class is unique and false otherwise */ private boolean isUniqueClass(AnnotatedTypeMirror type) { Element el = types.asElement(type.getUnderlyingType()); if (el == null) { return false; } AnnotationMirrorSet annoMirrors = atypeFactory.getDeclAnnotations(el); if (annoMirrors == null) { return false; } if (atypeFactory.containsSameByClass(annoMirrors, Unique.class)) { return true; } return false; } /** * Returns true if the enclosing method is a constructor whose return type is annotated as * {@code @Unique}. * * @return true if the enclosing method is a constructor whose return type is annotated as * {@code @Unique} */ private boolean isInUniqueConstructor() { MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); if (enclosingMethod == null) { return false; // No enclosing method. } return TreeUtils.isConstructor(enclosingMethod) && atypeFactory .getAnnotatedType(enclosingMethod) .getReturnType() .hasAnnotation(Unique.class); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy