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

org.checkerframework.framework.flow.CFAbstractTransfer Maven / Gradle / Ivy

package org.checkerframework.framework.flow;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;

import org.checkerframework.checker.interning.qual.InternedDistinct;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;
import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.ForwardTransferFunction;
import org.checkerframework.dataflow.analysis.RegularTransferResult;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
import org.checkerframework.dataflow.cfg.node.AbstractNodeVisitor;
import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.CaseNode;
import org.checkerframework.dataflow.cfg.node.ClassNameNode;
import org.checkerframework.dataflow.cfg.node.ConditionalNotNode;
import org.checkerframework.dataflow.cfg.node.DeconstructorPatternNode;
import org.checkerframework.dataflow.cfg.node.EqualToNode;
import org.checkerframework.dataflow.cfg.node.ExpressionStatementNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.InstanceOfNode;
import org.checkerframework.dataflow.cfg.node.LambdaResultExpressionNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.NarrowingConversionNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.NotEqualNode;
import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
import org.checkerframework.dataflow.cfg.node.StringConversionNode;
import org.checkerframework.dataflow.cfg.node.SwitchExpressionNode;
import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
import org.checkerframework.dataflow.cfg.node.ThisNode;
import org.checkerframework.dataflow.cfg.node.VariableDeclarationNode;
import org.checkerframework.dataflow.cfg.node.WideningConversionNode;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.flow.CFAbstractAnalysis.FieldInitialValue;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.util.Contract;
import org.checkerframework.framework.util.Contract.ConditionalPostcondition;
import org.checkerframework.framework.util.Contract.Postcondition;
import org.checkerframework.framework.util.Contract.Precondition;
import org.checkerframework.framework.util.ContractsFromMethod;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.framework.util.StringToJavaExpression;
import org.checkerframework.javacutil.AnnotationMirrorSet;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;

/**
 * The default analysis transfer function for the Checker Framework. It propagates information
 * through assignments. It uses the {@link AnnotatedTypeFactory} to provide checker-specific logic
 * to combine types (e.g., what is the type of a string concatenation, given the types of the two
 * operands) and acts as an abstraction function (e.g., determine the annotations on literals).
 *
 * 

Design note: CFAbstractTransfer and its subclasses are supposed to act as transfer functions. * But, since the AnnotatedTypeFactory already existed and performed checker-independent type * propagation, CFAbstractTransfer delegates work to it instead of duplicating some logic in * CFAbstractTransfer. The checker-specific subclasses of CFAbstractTransfer do implement transfer * function logic themselves. */ public abstract class CFAbstractTransfer< V extends CFAbstractValue, S extends CFAbstractStore, T extends CFAbstractTransfer> extends AbstractNodeVisitor, TransferInput> implements ForwardTransferFunction { /** The analysis used by this transfer function. */ protected final CFAbstractAnalysis analysis; /** * Should the analysis use sequential Java semantics (i.e., assume that only one thread is * running at all times)? */ protected final boolean sequentialSemantics; /* NO-AFU Indicates that the whole-program inference is on. */ /* NO-AFU private final boolean infer; */ /** * Create a CFAbstractTransfer. * * @param analysis the analysis used by this transfer function */ protected CFAbstractTransfer(CFAbstractAnalysis analysis) { this(analysis, false); } /** * Constructor that allows forcing concurrent semantics to be on for this instance of * CFAbstractTransfer. * * @param analysis the analysis used by this transfer function * @param forceConcurrentSemantics whether concurrent semantics should be forced to be on. If * false, concurrent semantics are turned off by default, but the user can still turn them * on via {@code -AconcurrentSemantics}. If true, the user cannot turn off concurrent * semantics. */ protected CFAbstractTransfer( CFAbstractAnalysis analysis, boolean forceConcurrentSemantics) { this.analysis = analysis; this.sequentialSemantics = !(forceConcurrentSemantics || analysis.checker.hasOption("concurrentSemantics")); /* NO-AFU this.infer = analysis.checker.hasOption("infer"); */ } /** * Returns true if the transfer function uses sequential semantics, false if it uses concurrent * semantics. Useful when creating an empty store, since a store makes different decisions * depending on whether sequential or concurrent semantics are used. * * @return true if the transfer function uses sequential semantics, false if it uses concurrent * semantics */ @Pure public boolean usesSequentialSemantics() { return sequentialSemantics; } /** * A hook for subclasses to modify the result of the transfer function. This method is called * before returning the abstract value {@code value} as the result of the transfer function. * *

If a subclass overrides this method, the subclass should also override {@link * #finishValue(CFAbstractValue,CFAbstractStore,CFAbstractStore)}. * * @param value a value to possibly modify * @param store the store * @return the possibly-modified value */ @SideEffectFree protected @Nullable V finishValue(@Nullable V value, S store) { return value; } /** * A hook for subclasses to modify the result of the transfer function. This method is called * before returning the abstract value {@code value} as the result of the transfer function. * *

If a subclass overrides this method, the subclass should also override {@link * #finishValue(CFAbstractValue,CFAbstractStore)}. * * @param value the value to finish * @param thenStore the "then" store * @param elseStore the "else" store * @return the possibly-modified value */ @SideEffectFree protected @Nullable V finishValue(@Nullable V value, S thenStore, S elseStore) { return value; } /** * Returns the abstract value of a non-leaf tree {@code tree}, as computed by the {@link * AnnotatedTypeFactory}. * * @return the abstract value of a non-leaf tree {@code tree}, as computed by the {@link * AnnotatedTypeFactory} */ protected V getValueFromFactory(Tree tree, Node node) { GenericAnnotatedTypeFactory> factory = analysis.atypeFactory; Tree preTree = analysis.getCurrentTree(); analysis.setCurrentTree(tree); AnnotatedTypeMirror at; if (node instanceof MethodInvocationNode && ((MethodInvocationNode) node).getIterableExpression() != null) { ExpressionTree iter = ((MethodInvocationNode) node).getIterableExpression(); at = factory.getIterableElementType(iter); } else if (node instanceof ArrayAccessNode && ((ArrayAccessNode) node).getArrayExpression() != null) { ExpressionTree array = ((ArrayAccessNode) node).getArrayExpression(); at = factory.getIterableElementType(array); } else { at = factory.getAnnotatedType(tree); } analysis.setCurrentTree(preTree); return analysis.createAbstractValue(at); } /** The fixed initial store. */ private @Nullable S fixedInitialStore = null; /** * Set a fixed initial Store. * * @param s initial store; possible null */ public void setFixedInitialStore(@Nullable S s) { fixedInitialStore = s; } /** The initial store maps method formal parameters to their currently most refined type. */ @Override public S initialStore(UnderlyingAST underlyingAST, List parameters) { if (underlyingAST.getKind() != UnderlyingAST.Kind.LAMBDA && underlyingAST.getKind() != UnderlyingAST.Kind.METHOD) { if (fixedInitialStore != null) { return fixedInitialStore; } else { return analysis.createEmptyStore(sequentialSemantics); } } S store; AnnotatedTypeFactory atypeFactory = analysis.getTypeFactory(); if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { if (fixedInitialStore != null) { // copy knowledge store = analysis.createCopiedStore(fixedInitialStore); } else { store = analysis.createEmptyStore(sequentialSemantics); } for (LocalVariableNode p : parameters) { AnnotatedTypeMirror anno = atypeFactory.getAnnotatedType(p.getElement()); store.initializeMethodParameter(p, analysis.createAbstractValue(anno)); } // add properties known through precondition CFGMethod method = (CFGMethod) underlyingAST; MethodTree methodDeclTree = method.getMethod(); ExecutableElement methodElem = TreeUtils.elementFromDeclaration(methodDeclTree); addInformationFromPreconditions( store, atypeFactory, method, methodDeclTree, methodElem); addInitialFieldValues(store, method.getClassTree(), methodDeclTree); addFinalLocalValues(store, methodElem); /* NO-AFU if (shouldPerformWholeProgramInference(methodDeclTree, methodElem)) { Map overriddenMethods = AnnotatedTypes.overriddenMethods( atypeFactory.getElementUtils(), atypeFactory, methodElem); for (Map.Entry pair : overriddenMethods.entrySet()) { AnnotatedExecutableType overriddenMethod = AnnotatedTypes.asMemberOf( atypeFactory.getProcessingEnv().getTypeUtils(), atypeFactory, pair.getKey(), pair.getValue()); // Infers parameter and receiver types of the method based // on the overridden method. atypeFactory .getWholeProgramInference() .updateFromOverride(methodDeclTree, methodElem, overriddenMethod); } } */ } else if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { if (fixedInitialStore != null) { // Create a copy and keep only the field values (nothing else applies). store = analysis.createCopiedStore(fixedInitialStore); // Allow that local variables are retained; they are effectively final, // otherwise Java wouldn't allow access from within the lambda. // TODO: what about the other information? Can code further down be simplified? // store.localVariableValues.clear(); store.classValues.clear(); store.arrayValues.clear(); store.methodValues.clear(); } else { store = analysis.createEmptyStore(sequentialSemantics); } for (LocalVariableNode p : parameters) { AnnotatedTypeMirror anno = atypeFactory.getAnnotatedType(p.getElement()); store.initializeMethodParameter(p, analysis.createAbstractValue(anno)); } CFGLambda lambda = (CFGLambda) underlyingAST; @SuppressWarnings("interning:assignment.type.incompatible") // used in == tests @InternedDistinct Tree enclosingTree = TreePathUtil.enclosingOfKind( atypeFactory.getPath(lambda.getLambdaTree()), TreeUtils.classAndMethodTreeKinds()); Element enclosingElement = null; if (enclosingTree.getKind() == Tree.Kind.METHOD) { // If it is in an initializer, we need to use locals from the initializer. enclosingElement = TreeUtils.elementFromDeclaration((MethodTree) enclosingTree); } else if (TreeUtils.isClassTree(enclosingTree)) { // Try to find an enclosing initializer block. // Would love to know if there was a better way. // Find any enclosing element of the lambda (using trees). // Then go up the elements to find an initializer element (which can't be found with // the tree). TreePath loopTree = atypeFactory.getPath(lambda.getLambdaTree()).getParentPath(); Element anEnclosingElement = null; while (loopTree.getLeaf() != enclosingTree) { Element sym = TreeUtils.elementFromTree(loopTree.getLeaf()); if (sym != null) { anEnclosingElement = sym; break; } loopTree = loopTree.getParentPath(); } while (anEnclosingElement != null && !anEnclosingElement.equals(TreeUtils.elementFromTree(enclosingTree))) { if (anEnclosingElement.getKind() == ElementKind.INSTANCE_INIT || anEnclosingElement.getKind() == ElementKind.STATIC_INIT) { enclosingElement = anEnclosingElement; break; } anEnclosingElement = anEnclosingElement.getEnclosingElement(); } } if (enclosingElement != null) { addFinalLocalValues(store, enclosingElement); } // We want the initialization stuff, but need to throw out any refinements. Map fieldValuesClone = new HashMap<>(store.fieldValues); for (Map.Entry fieldValue : fieldValuesClone.entrySet()) { AnnotatedTypeMirror declaredType = atypeFactory.getAnnotatedType(fieldValue.getKey().getField()); V lubbedValue = analysis.createAbstractValue(declaredType) .leastUpperBound(fieldValue.getValue()); store.fieldValues.put(fieldValue.getKey(), lubbedValue); } } else { assert false : "Unexpected tree: " + underlyingAST; store = null; } return store; } /** * Add field values to the initial store before {@code methodTree}. * *

The initializer value is inserted into {@code store} if the field is final and the field * type is immutable, as defined by {@link AnnotatedTypeFactory#isImmutable(TypeMirror)}. * *

The declared value is inserted into {@code store} if: * *

    *
  • {@code methodTree} is a constructor and the field has an initializer. (Use the * declaration type rather than the initializer because an initialization block might have * re-set it.) *
  • {@code methodTree} is not a constructor and the receiver is fully initialized as * determined by {@link #isNotFullyInitializedReceiver(MethodTree)}. *
* * @param store initial store into which field values are inserted; it may not be empty * @param classTree the class that contains {@code methodTree} * @param methodTree the method or constructor tree */ // TODO: should field visibility matter? An access from outside the class might observe // the declared type instead of a refined type. Issue a warning to alert users? private void addInitialFieldValues(S store, ClassTree classTree, MethodTree methodTree) { boolean isConstructor = TreeUtils.isConstructor(methodTree); boolean isStaticMethod = ElementUtils.isStatic(TreeUtils.elementFromDeclaration(methodTree)); TypeElement classEle = TreeUtils.elementFromDeclaration(classTree); for (FieldInitialValue fieldInitialValue : analysis.getFieldInitialValues()) { VariableElement varEle = fieldInitialValue.fieldDecl.getField(); boolean isStaticField = ElementUtils.isStatic(varEle); if (isStaticMethod && !isStaticField) { continue; } // Insert the value from the initializer of private final fields. if (fieldInitialValue.initializer != null // && varEle.getModifiers().contains(Modifier.PRIVATE) && ElementUtils.isFinal(varEle) && analysis.atypeFactory.isImmutable(ElementUtils.getType(varEle))) { store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.initializer); // The type from the initializer is always more specific than (or equal to) the // declared type of the field. // So, if there is an initializer, there is no point in inserting the declared type // below. continue; } boolean isFieldOfCurrentClass = varEle.getEnclosingElement().equals(classEle); // Maybe insert the declared type: if (!isConstructor) { // If it's not a constructor, use the declared type if the receiver of the method is // fully initialized. boolean isInitializedReceiver = !isNotFullyInitializedReceiver(methodTree); if (isInitializedReceiver && isFieldOfCurrentClass) { store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.declared); } } else { // If it is a constructor, then only use the declared type if the field has been // initialized. if (fieldInitialValue.initializer != null && isFieldOfCurrentClass) { store.insertValue(fieldInitialValue.fieldDecl, fieldInitialValue.declared); } } } } /** * Adds information about effectively final variables (from outer scopes) * * @param store the store to add to * @param enclosingElement the enclosing element of the code we are analyzing */ private void addFinalLocalValues(S store, Element enclosingElement) { // add information about effectively final variables (from outer scopes) for (Map.Entry e : analysis.atypeFactory.getFinalLocalValues().entrySet()) { VariableElement elem = e.getKey(); // TODO: There is a design flaw where the values of final local values leaks // into other methods of the same class. For example, in // class a { void b() {...} void c() {...} } // final local values from b() would be visible in the store for c(), // even though they should only be visible in b() and in classes // defined inside the method body of b(). // This is partly because GenericAnnotatedTypeFactory.performFlowAnalysis does not call // itself recursively to analyze inner classes, but instead pops classes off of a queue, // and the information about known final local values is stored by // GenericAnnotatedTypeFactory.analyze in GenericAnnotatedTypeFactory.flowResult, which // is visible to all classes in the queue regardless of their level of recursion. // We work around this here by ensuring that we only add a final local value to a // method's store if that method is enclosed by the method where the local variables // were declared. // Find the enclosing method of the element Element enclosingMethodOfVariableDeclaration = elem.getEnclosingElement(); if (enclosingMethodOfVariableDeclaration != null) { // Now find all the enclosing methods of the code we are analyzing. If any one of // them matches the above, then the final local variable value applies. Element enclosingMethodOfCurrentMethod = enclosingElement; while (enclosingMethodOfCurrentMethod != null) { if (enclosingMethodOfVariableDeclaration.equals( enclosingMethodOfCurrentMethod)) { LocalVariable l = new LocalVariable(elem); store.insertValue(l, e.getValue()); break; } enclosingMethodOfCurrentMethod = enclosingMethodOfCurrentMethod.getEnclosingElement(); } } } } /** * Returns true if the receiver of a method or constructor might not be fully initialized * according to {@code analysis.atypeFactory.isNotFullyInitializedReceiver(methodDeclTree)}. * * @param methodDeclTree the declaration of the method or constructor * @return true if the receiver of a method or constructor might not be fully initialized * @see GenericAnnotatedTypeFactory#isNotFullyInitializedReceiver(MethodTree) */ @Pure protected final boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { return analysis.atypeFactory.isNotFullyInitializedReceiver(methodDeclTree); } /** * Add the information from all the preconditions of a method to the initial store in the method * body. * * @param initialStore the initial store for the method body * @param factory the type factory * @param methodAst the AST for a method declaration * @param methodDeclTree the declaration of the method; is a field of {@code methodAst} * @param methodElement the element for the method */ protected void addInformationFromPreconditions( S initialStore, AnnotatedTypeFactory factory, CFGMethod methodAst, MethodTree methodDeclTree, ExecutableElement methodElement) { ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); Set preconditions = contractsUtils.getPreconditions(methodElement); StringToJavaExpression stringToJavaExpr = stringExpr -> StringToJavaExpression.atMethodBody( stringExpr, methodDeclTree, analysis.checker); for (Precondition p : preconditions) { String stringExpr = p.expressionString; AnnotationMirror annotation = p.viewpointAdaptDependentTypeAnnotation( analysis.atypeFactory, stringToJavaExpr, /* errorTree= */ null); JavaExpression exprJe; try { // TODO: currently, these expressions are parsed at the declaration (i.e. here) and // for every use. this could be optimized to store the result the first time. // (same for other annotations) exprJe = StringToJavaExpression.atMethodBody( stringExpr, methodDeclTree, analysis.checker); } catch (JavaExpressionParseException e) { // Errors are reported by BaseTypeVisitor.checkContractsAtMethodDeclaration(). continue; } initialStore.insertValuePermitNondeterministic(exprJe, annotation); } } /** * The default visitor returns the input information unchanged, or in the case of conditional * input information, merged. */ @Override public TransferResult visitNode(Node n, TransferInput in) { V value = null; // TODO: handle implicit/explicit this and go to correct factory method Tree tree = n.getTree(); if (tree != null) { if (TreeUtils.canHaveTypeAnnotation(tree)) { value = getValueFromFactory(tree, n); } } return createTransferResult(value, in); } /** * Creates a TransferResult. * *

This default implementation returns the input information unchanged, or in the case of * conditional input information, merged. * * @param value the value; possibly null * @param in the transfer input * @return the input information, as a TransferResult */ @SideEffectFree protected TransferResult createTransferResult(@Nullable V value, TransferInput in) { if (in.containsTwoStores()) { S thenStore = in.getThenStore(); S elseStore = in.getElseStore(); return new ConditionalTransferResult<>( finishValue(value, thenStore, elseStore), thenStore, elseStore); } else { S store = in.getRegularStore(); return new RegularTransferResult<>(finishValue(value, store), store); } } /** * Creates a TransferResult just like the given one, but with the given value. * *

This default implementation returns the input information unchanged, or in the case of * conditional input information, merged. * * @param value the value; possibly null * @param in the TransferResult to copy * @return the input information */ @SideEffectFree protected TransferResult recreateTransferResult( @Nullable V value, TransferResult in) { if (in.containsTwoStores()) { S thenStore = in.getThenStore(); S elseStore = in.getElseStore(); return new ConditionalTransferResult<>( finishValue(value, thenStore, elseStore), thenStore, elseStore); } else { S store = in.getRegularStore(); return new RegularTransferResult<>(finishValue(value, store), store); } } @Override public TransferResult visitClassName(ClassNameNode n, TransferInput in) { // The tree underlying a class name is a type tree. V value = null; Tree tree = n.getTree(); if (tree != null) { if (TreeUtils.canHaveTypeAnnotation(tree)) { GenericAnnotatedTypeFactory> factory = analysis.atypeFactory; analysis.setCurrentTree(tree); AnnotatedTypeMirror at = factory.getAnnotatedTypeFromTypeTree(tree); analysis.setCurrentTree(null); value = analysis.createAbstractValue(at); } } return createTransferResult(value, in); } @Override public TransferResult visitFieldAccess(FieldAccessNode n, TransferInput p) { S store = p.getRegularStore(); V storeValue = store.getValue(n); // look up value in factory, and take the more specific one // TODO: handle cases, where this is not allowed (e.g. constructors in non-null type // systems) V factoryValue = getValueFromFactory(n.getTree(), n); V value = moreSpecificValue(factoryValue, storeValue); return new RegularTransferResult<>(finishValue(value, store), store); } @Override public TransferResult visitArrayAccess(ArrayAccessNode n, TransferInput p) { S store = p.getRegularStore(); V storeValue = store.getValue(n); // look up value in factory, and take the more specific one V factoryValue = getValueFromFactory(n.getTree(), n); V value = moreSpecificValue(factoryValue, storeValue); return new RegularTransferResult<>(finishValue(value, store), store); } /** Use the most specific type information available according to the store. */ @Override public TransferResult visitLocalVariable(LocalVariableNode n, TransferInput in) { S store = in.getRegularStore(); V valueFromStore = store.getValue(n); V valueFromFactory = getValueFromFactory(n.getTree(), n); V value = moreSpecificValue(valueFromFactory, valueFromStore); return new RegularTransferResult<>(finishValue(value, store), store); } @Override public TransferResult visitThis(ThisNode n, TransferInput in) { S store = in.getRegularStore(); V valueFromStore = store.getValue(n); V valueFromFactory = null; V value = null; Tree tree = n.getTree(); if (tree != null && TreeUtils.canHaveTypeAnnotation(tree)) { valueFromFactory = getValueFromFactory(tree, n); } if (valueFromFactory == null) { value = valueFromStore; } else { value = moreSpecificValue(valueFromFactory, valueFromStore); } return new RegularTransferResult<>(finishValue(value, store), store); } @Override public TransferResult visitTernaryExpression( TernaryExpressionNode n, TransferInput p) { TransferResult result = super.visitTernaryExpression(n, p); S thenStore = result.getThenStore(); S elseStore = result.getElseStore(); V thenValue = p.getValueOfSubNode(n.getThenOperand()); V elseValue = p.getValueOfSubNode(n.getElseOperand()); V resultValue = null; if (thenValue != null && elseValue != null) { // If a conditional expression is a poly expression, then its Java type is the type of // its context. (For example, the type of the conditional expression in `Object o = b ? // "" : "";` is `Object`, not `String`.) // So, use the Java type of the conditional expression and the annotations for each // branch. TypeMirror conditionalType = TreeUtils.typeOf(n.getTree()); // The resulting abstract value is the merge of the 'then' and 'else' branch. resultValue = thenValue.leastUpperBound(elseValue, conditionalType); } V finishedValue = finishValue(resultValue, thenStore, elseStore); return new ConditionalTransferResult<>(finishedValue, thenStore, elseStore); } @Override public TransferResult visitSwitchExpressionNode( SwitchExpressionNode n, TransferInput vsTransferInput) { return visitLocalVariable(n.getSwitchExpressionVar(), vsTransferInput); } /** Reverse the role of the 'thenStore' and 'elseStore'. */ @Override public TransferResult visitConditionalNot(ConditionalNotNode n, TransferInput p) { TransferResult result = super.visitConditionalNot(n, p); S thenStore = result.getThenStore(); S elseStore = result.getElseStore(); return new ConditionalTransferResult<>(result.getResultValue(), elseStore, thenStore); } @Override public TransferResult visitEqualTo(EqualToNode n, TransferInput p) { TransferResult res = super.visitEqualTo(n, p); Node leftN = n.getLeftOperand(); Node rightN = n.getRightOperand(); V leftV = p.getValueOfSubNode(leftN); V rightV = p.getValueOfSubNode(rightN); if (res.containsTwoStores() && (NodeUtils.isConstantBoolean(leftN, false) || NodeUtils.isConstantBoolean(rightN, false))) { S thenStore = res.getElseStore(); S elseStore = res.getThenStore(); res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); } // if annotations differ, use the one that is more precise for both // sides (and add it to the store if possible) res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false); res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false); return res; } @Override public TransferResult visitNotEqual(NotEqualNode n, TransferInput p) { TransferResult res = super.visitNotEqual(n, p); Node leftN = n.getLeftOperand(); Node rightN = n.getRightOperand(); V leftV = p.getValueOfSubNode(leftN); V rightV = p.getValueOfSubNode(rightN); if (res.containsTwoStores() && (NodeUtils.isConstantBoolean(leftN, true) || NodeUtils.isConstantBoolean(rightN, true))) { S thenStore = res.getElseStore(); S elseStore = res.getThenStore(); res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore); } // if annotations differ, use the one that is more precise for both // sides (and add it to the store if possible) res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true); res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true); return res; } /** * Refine the annotation of {@code secondNode} if the annotation {@code secondValue} is less * precise than {@code firstValue}. This is possible, if {@code secondNode} is an expression * that is tracked by the store (e.g., a local variable or a field). Clients usually call this * twice with {@code firstNode} and {@code secondNode} reversed, to refine each of them. * *

Note that when overriding this method, when a new type is inserted into the store, {@link * #splitAssignments} should be called, and the new type should be inserted into the store for * each of the resulting nodes. * * @param firstNode the node that might be more precise * @param secondNode the node whose type to possibly refine * @param firstValue the abstract value that might be more precise * @param secondValue the abstract value that might be less precise * @param res the previous result * @param notEqualTo if true, indicates that the logic is flipped (i.e., the information is * added to the {@code elseStore} instead of the {@code thenStore}) for a not-equal * comparison. * @return the conditional transfer result (if information has been added), or {@code res} */ protected TransferResult strengthenAnnotationOfEqualTo( TransferResult res, Node firstNode, Node secondNode, V firstValue, V secondValue, boolean notEqualTo) { if (firstValue != null) { // Only need to insert if the second value is actually different. if (!firstValue.equals(secondValue)) { List secondParts = splitAssignments(secondNode); for (Node secondPart : secondParts) { JavaExpression secondInternal = JavaExpression.fromNode(secondPart); if (!secondInternal.isDeterministic(analysis.atypeFactory)) { continue; } if (CFAbstractStore.canInsertJavaExpression(secondInternal)) { S thenStore = res.getThenStore(); S elseStore = res.getElseStore(); if (notEqualTo) { elseStore.insertValue(secondInternal, firstValue); } else { thenStore.insertValue(secondInternal, firstValue); } // To handle `(a = b = c) == x`, repeat for all insertable receivers of // splitted assignments instead of returning. res = new ConditionalTransferResult<>( res.getResultValue(), thenStore, elseStore); } } } } return res; } /** * Takes a node, and either returns the node itself again (as a singleton list), or if the node * is an assignment node, returns the lhs and rhs (where splitAssignments is applied recursively * to the rhs -- that is, it is possible that the rhs does not appear in the result, but rather * its lhs and rhs do). * * @param node possibly an assignment node * @return a list containing all the right- and left-hand sides in the given assignment node; it * contains just the node itself if it is not an assignment) */ @SideEffectFree protected List splitAssignments(Node node) { if (node instanceof AssignmentNode) { List result = new ArrayList<>(2); AssignmentNode a = (AssignmentNode) node; result.add(a.getTarget()); result.addAll(splitAssignments(a.getExpression())); return result; } else { return Collections.singletonList(node); } } @Override public TransferResult visitAssignment(AssignmentNode n, TransferInput in) { Node lhs = n.getTarget(); Node rhs = n.getExpression(); V rhsValue = in.getValueOfSubNode(rhs); /* NO-AFU if (shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) { // Fields defined in interfaces are LocalVariableNodes with ElementKind of FIELD. if (lhs instanceof FieldAccessNode || (lhs instanceof LocalVariableNode && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.FIELD)) { // Updates inferred field type analysis.atypeFactory.getWholeProgramInference().updateFromFieldAssignment(lhs, rhs); } else if (lhs instanceof LocalVariableNode && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.PARAMETER) { // lhs is a formal parameter of some method VariableElement param = ((LocalVariableNode) lhs).getElement(); analysis .atypeFactory .getWholeProgramInference() .updateFromFormalParameterAssignment((LocalVariableNode) lhs, rhs, param); } */ if (n.isSynthetic() && in.containsTwoStores()) { // This is a synthetic assignment node created for a ternary expression. In this case // the `then` and `else` store are not merged. S thenStore = in.getThenStore(); S elseStore = in.getElseStore(); processCommonAssignment(in, lhs, rhs, thenStore, rhsValue); processCommonAssignment(in, lhs, rhs, elseStore, rhsValue); return new ConditionalTransferResult<>( finishValue(rhsValue, thenStore, elseStore), thenStore, elseStore); } else { S store = in.getRegularStore(); processCommonAssignment(in, lhs, rhs, store, rhsValue); return new RegularTransferResult<>(finishValue(rhsValue, store), store); } } @Override public TransferResult visitReturn(ReturnNode n, TransferInput p) { TransferResult result = super.visitReturn(n, p); /* NO-AFU if (shouldPerformWholeProgramInference(n.getTree())) { // Retrieves class containing the method ClassTree classTree = analysis.getContainingClass(n.getTree()); // classTree is null e.g. if this is a return statement in a lambda. if (classTree == null) { return result; } ClassSymbol classSymbol = (ClassSymbol) TreeUtils.elementFromDeclaration(classTree); ExecutableElement methodElem = TreeUtils.elementFromDeclaration(analysis.getContainingMethod(n.getTree())); Map overriddenMethods = AnnotatedTypes.overriddenMethods( analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem); // Updates the inferred return type of the method analysis .atypeFactory .getWholeProgramInference() .updateFromReturn( n, classSymbol, analysis.getContainingMethod(n.getTree()), overriddenMethods); } */ return result; } @Override public TransferResult visitLambdaResultExpression( LambdaResultExpressionNode n, TransferInput in) { return n.getResult().accept(this, in); } /** * Determine abstract value of right-hand side and update the store accordingly. * * @param in the store(s) before the assignment * @param lhs the left-hand side of the assignment * @param rhs the right-hand side of the assignment * @param store the regular input store (from {@code in}) * @param rhsValue the value of the right-hand side of the assignment */ protected void processCommonAssignment( TransferInput in, Node lhs, Node rhs, S store, V rhsValue) { // update information in the store store.updateForAssignment(lhs, rhsValue); } @Override public TransferResult visitObjectCreation(ObjectCreationNode n, TransferInput p) { /* NO-AFU if (shouldPerformWholeProgramInference(n.getTree())) { NewClassTree newClassTree = n.getTree(); // Can't infer annotations on an anonymous constructor, so use the super constructor. ExecutableElement constructorElt = TreeUtils.getSuperConstructor(newClassTree); if (newClassTree.getClassBody() == null || !TreeUtils.hasSyntheticArgument(newClassTree)) { // TODO: WPI could be changed to handle the synthetic argument, but for now just // don't infer annotations for those new class trees. analysis .atypeFactory .getWholeProgramInference() .updateFromObjectCreation(n, constructorElt, p.getRegularStore()); } } */ NewClassTree newClassTree = n.getTree(); ExecutableElement constructorElt = TreeUtils.getSuperConstructor(newClassTree); S store = p.getRegularStore(); // add new information based on postcondition processPostconditions(n, store, constructorElt, newClassTree); return super.visitObjectCreation(n, p); } @Override public TransferResult visitMethodInvocation( MethodInvocationNode n, TransferInput in) { S store = in.getRegularStore(); ExecutableElement method = n.getTarget().getMethod(); /* NO-AFU // Perform WPI before the store has been side-effected. if (shouldPerformWholeProgramInference(n.getTree(), method)) { // Updates the inferred parameter types of the invoked method. analysis.atypeFactory .getWholeProgramInference() .updateFromMethodInvocation(n, method, store); } */ ExpressionTree invocationTree = n.getTree(); // Determine the abstract value for the method call. // look up the call's value from factory V factoryValue = (invocationTree == null) ? null : getValueFromFactory(invocationTree, n); // look up the call's value in the store (if possible) V storeValue = store.getValue(n); V resValue = moreSpecificValue(factoryValue, storeValue); store.updateForMethodCall(n, analysis.atypeFactory, resValue); // add new information based on postcondition processPostconditions(n, store, method, invocationTree); if (TypesUtils.isBooleanType(method.getReturnType())) { S thenStore = store; S elseStore = thenStore.copy(); // add new information based on conditional postcondition processConditionalPostconditions(n, method, invocationTree, thenStore, elseStore); return new ConditionalTransferResult<>( finishValue(resValue, thenStore, elseStore), thenStore, elseStore); } else { return new RegularTransferResult<>(finishValue(resValue, store), store); } } @Override public TransferResult visitDeconstructorPattern( DeconstructorPatternNode n, TransferInput in) { // TODO: Implement getting the type of a DeconstructorPatternTree. V value = null; return createTransferResult(value, in); } @Override public TransferResult visitInstanceOf(InstanceOfNode node, TransferInput in) { TransferResult result = super.visitInstanceOf(node, in); for (LocalVariableNode bindingVar : node.getBindingVariables()) { JavaExpression expr = JavaExpression.fromNode(bindingVar); AnnotatedTypeMirror expType = analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression()); for (AnnotationMirror anno : expType.getAnnotations()) { in.getRegularStore().insertOrRefine(expr, anno); } } // The "reference type" is the type after "instanceof". Tree refTypeTree = node.getTree().getType(); if (refTypeTree != null && refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) { AnnotatedTypeMirror refType = analysis.atypeFactory.getAnnotatedType(refTypeTree); AnnotatedTypeMirror expType = analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression()); if (analysis.atypeFactory.getTypeHierarchy().isSubtype(refType, expType) && !refType.getAnnotations().equals(expType.getAnnotations()) && !expType.getAnnotations().isEmpty()) { JavaExpression expr = JavaExpression.fromTree(node.getTree().getExpression()); for (AnnotationMirror anno : refType.getAnnotations()) { in.getRegularStore().insertOrRefine(expr, anno); } return new RegularTransferResult<>(result.getResultValue(), in.getRegularStore()); } } return result; } /* NO-AFU * Returns true if whole-program inference should be performed. If the tree is in the scope of * a @SuppressWarnings, then this method returns false. * * @param tree a tree * @return whether to perform whole-program inference on the tree */ /* NO-AFU protected boolean shouldPerformWholeProgramInference(Tree tree) { TreePath path = this.analysis.atypeFactory.getPath(tree); return infer && (tree == null || !analysis.checker.shouldSuppressWarnings(path, "")); } */ /* NO-AFU * Returns true if whole-program inference should be performed. If the expressionTree or lhsTree * is in the scope of a @SuppressWarnings, then this method returns false. * * @param expressionTree the right-hand side of an assignment * @param lhsTree the left-hand side of an assignment * @return whether to perform whole-program inference */ /* NO-AFU protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { // Check that infer is true and the tree isn't in scope of a @SuppressWarnings // before calling InternalUtils.symbol(lhs). if (!shouldPerformWholeProgramInference(expressionTree)) { return false; } Element elt = TreeUtils.elementFromTree(lhsTree); return !analysis.checker.shouldSuppressWarnings(elt, ""); } */ /* NO-AFU * Returns true if whole-program inference should be performed. If the tree or element is in the * scope of a @SuppressWarnings, then this method returns false. * * @param tree a tree * @param elt its element * @return whether to perform whole-program inference */ /* NO-AFU private boolean shouldPerformWholeProgramInference(Tree tree, Element elt) { return shouldPerformWholeProgramInference(tree) && !analysis.checker.shouldSuppressWarnings(elt, ""); } */ /** * Add information from the postconditions of a method to the store after an invocation. * * @param invocationNode a method call or an object creation * @param store a store; is side-effected by this method * @param executableElement the method or constructor being called * @param invocationTree the tree for the method call or for the object creation */ protected void processPostconditions( Node invocationNode, S store, ExecutableElement executableElement, ExpressionTree invocationTree) { ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); Set postconditions = contractsUtils.getPostconditions(executableElement); processPostconditionsAndConditionalPostconditions( invocationNode, invocationTree, store, null, postconditions); } /** * Add information from the conditional postconditions of a method to the stores after an * invocation. * * @param invocationNode a method call * @param methodElement the method being called * @param invocationTree the tree for the method call * @param thenStore the "then" store; is side-effected by this method * @param elseStore the "else" store; is side-effected by this method */ protected void processConditionalPostconditions( MethodInvocationNode invocationNode, ExecutableElement methodElement, ExpressionTree invocationTree, S thenStore, S elseStore) { ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); Set conditionalPostconditions = contractsUtils.getConditionalPostconditions(methodElement); processPostconditionsAndConditionalPostconditions( invocationNode, invocationTree, thenStore, elseStore, conditionalPostconditions); } /** * Add information from the postconditions and conditional postconditions of a method to the * stores after an invocation. * * @param invocationNode a method call node or an object creation node * @param invocationTree the tree for the method call or for the object creation * @param thenStore the "then" store; is side-effected by this method * @param elseStore the "else" store; is side-effected by this method * @param postconditions the postconditions */ private void processPostconditionsAndConditionalPostconditions( Node invocationNode, ExpressionTree invocationTree, S thenStore, S elseStore, Set postconditions) { StringToJavaExpression stringToJavaExpr = null; if (invocationNode instanceof MethodInvocationNode) { stringToJavaExpr = stringExpr -> StringToJavaExpression.atMethodInvocation( stringExpr, (MethodInvocationNode) invocationNode, analysis.checker); } else if (invocationNode instanceof ObjectCreationNode) { stringToJavaExpr = stringExpr -> StringToJavaExpression.atConstructorInvocation( stringExpr, (NewClassTree) invocationTree, analysis.checker); } else { throw new BugInCF( "CFAbstractTransfer.processPostconditionsAndConditionalPostconditions" + " expects a MethodInvocationNode or ObjectCreationNode argument;" + " received a " + invocationNode.getClass().getSimpleName()); } for (Contract p : postconditions) { // Viewpoint-adapt to the method use (the call site). AnnotationMirror anno = p.viewpointAdaptDependentTypeAnnotation( analysis.atypeFactory, stringToJavaExpr, /* errorTree= */ null); String expressionString = p.expressionString; try { JavaExpression je = stringToJavaExpr.toJavaExpression(expressionString); // "insertOrRefine" is called so that the postcondition information is added to any // existing information rather than replacing it. If the called method is not // side-effect-free, then the values that might have been changed by the method call // are removed from the store before this method is called. if (p.kind == Contract.Kind.CONDITIONALPOSTCONDITION) { if (((ConditionalPostcondition) p).resultValue) { thenStore.insertOrRefinePermitNondeterministic(je, anno); } else { elseStore.insertOrRefinePermitNondeterministic(je, anno); } } else { thenStore.insertOrRefinePermitNondeterministic(je, anno); } } catch (JavaExpressionParseException e) { // report errors here if (e.isFlowParseError()) { Object[] args = new Object[e.args.length + 1]; args[0] = ElementUtils.getSimpleSignature( (ExecutableElement) TreeUtils.elementFromUse(invocationTree)); System.arraycopy(e.args, 0, args, 1, e.args.length); analysis.checker.reportError( invocationTree, "flowexpr.parse.error.postcondition", args); } else { analysis.checker.report(invocationTree, e.getDiagMessage()); } } } } /** A case produces no value, but it may imply some facts about switch selector expression. */ @Override public TransferResult visitCase(CaseNode n, TransferInput in) { S store = in.getRegularStore(); TransferResult lubResult = null; // Case operands are the case constants. For example, A, B and C in case A, B, C:. // This method refines the type of the selector expression and the synthetic variable that // represents the selector expression to the type of the case constant if it is more // precise. // If there are multiple case constants then a new store is created for each case constant // and then they are lubbed. This method returns the lubbed result. for (Node caseOperand : n.getCaseOperands()) { TransferResult result = new ConditionalTransferResult<>( finishValue(null, store), in.getThenStore().copy(), in.getElseStore().copy(), false); V caseValue = in.getValueOfSubNode(caseOperand); AssignmentNode assign = n.getSwitchOperand(); V switchValue = store.getValue(JavaExpression.fromNode(assign.getTarget())); result = strengthenAnnotationOfEqualTo( result, caseOperand, assign.getExpression(), caseValue, switchValue, false); // Update value of switch temporary variable result = strengthenAnnotationOfEqualTo( result, caseOperand, assign.getTarget(), caseValue, switchValue, false); // Lub the result of one case label constant with the result of the others. if (lubResult == null) { lubResult = result; } else { S thenStore = lubResult.getThenStore().leastUpperBound(result.getThenStore()); S elseStore = lubResult.getElseStore().leastUpperBound(result.getElseStore()); lubResult = new ConditionalTransferResult<>( null, thenStore, elseStore, lubResult.storeChanged() || result.storeChanged()); } } return lubResult; } /** * In a cast {@code (@A C) e} of some expression {@code e} to a new type {@code @A C}, we * usually take the annotation of the type {@code C} (here {@code @A}). However, if the inferred * annotation of {@code e} is more precise, we keep that one. */ // @Override // public TransferResult visitTypeCast(TypeCastNode n, // TransferInput p) { // TransferResult result = super.visitTypeCast(n, p); // V value = result.getResultValue(); // V operandValue = p.getValueOfSubNode(n.getOperand()); // // Normally we take the value of the type cast node. However, if the old // // flow-refined value was more precise, we keep that value. // V resultValue = moreSpecificValue(value, operandValue); // result.setResultValue(resultValue); // return result; // } /** * Returns the abstract value of {@code (value1, value2)} that is more specific. If the two are * incomparable, then {@code value1} is returned. * * @param value1 an abstract value * @param value2 another abstract value * @return the more specific value of the two parameters, or, if they are incomparable, {@code * value1} */ @Pure public V moreSpecificValue(V value1, V value2) { if (value1 == null) { return value2; } if (value2 == null) { return value1; } return value1.mostSpecific(value2, value1); } @Override public TransferResult visitVariableDeclaration( VariableDeclarationNode n, TransferInput p) { S store = p.getRegularStore(); return new RegularTransferResult<>(finishValue(null, store), store); } @Override public TransferResult visitWideningConversion( WideningConversionNode n, TransferInput p) { TransferResult result = super.visitWideningConversion(n, p); // Combine annotations from the operand with the wide type V operandValue = p.getValueOfSubNode(n.getOperand()); V widenedValue = getWidenedValue(n.getType(), operandValue); result.setResultValue(widenedValue); return result; } /** * Returns an abstract value with the given {@code type} and the annotations from {@code * annotatedValue}, adapted for narrowing. This is only called at a narrowing conversion. * * @param type the type to narrow to * @param annotatedValue the type to narrow from * @return an abstract value with the given {@code type} and the annotations from {@code * annotatedValue}; returns null if {@code annotatedValue} is null */ @SideEffectFree protected @PolyNull V getNarrowedValue(TypeMirror type, @PolyNull V annotatedValue) { if (annotatedValue == null) { return null; } AnnotationMirrorSet narrowedAnnos = analysis.atypeFactory.getNarrowedAnnotations( annotatedValue.getAnnotations(), annotatedValue.getUnderlyingType().getKind(), type.getKind()); return analysis.createAbstractValue(narrowedAnnos, type); } /** * Returns an abstract value with the given {@code type} and the annotations from {@code * annotatedValue}, adapted for widening. This is only called at a widening conversion. * * @param type the type to widen to * @param annotatedValue the type to widen from * @return an abstract value with the given {@code type} and the annotations from {@code * annotatedValue}; returns null if {@code annotatedValue} is null */ @SideEffectFree protected @PolyNull V getWidenedValue(TypeMirror type, @PolyNull V annotatedValue) { if (annotatedValue == null) { return null; } AnnotationMirrorSet widenedAnnos = analysis.atypeFactory.getWidenedAnnotations( annotatedValue.getAnnotations(), annotatedValue.getUnderlyingType().getKind(), type.getKind()); return analysis.createAbstractValue(widenedAnnos, type); } @Override public TransferResult visitNarrowingConversion( NarrowingConversionNode n, TransferInput p) { TransferResult result = super.visitNarrowingConversion(n, p); // Combine annotations from the operand with the narrow type V operandValue = p.getValueOfSubNode(n.getOperand()); V narrowedValue = getNarrowedValue(n.getType(), operandValue); result.setResultValue(narrowedValue); return result; } @Override public TransferResult visitStringConversion( StringConversionNode n, TransferInput p) { TransferResult result = super.visitStringConversion(n, p); result.setResultValue(p.getValueOfSubNode(n.getOperand())); return result; } @Override public TransferResult visitExpressionStatement( ExpressionStatementNode n, TransferInput vsTransferInput) { // Merge the input S info = vsTransferInput.getRegularStore(); return new RegularTransferResult<>(finishValue(null, info), info); } /** * Inserts newAnno as the value into all stores (conditional or not) in the result for node. * This is a utility method for subclasses. * * @param result the TransferResult holding the stores to modify * @param target the receiver whose value should be modified * @param newAnno the new value */ protected static void insertIntoStores( TransferResult result, JavaExpression target, AnnotationMirror newAnno) { if (result.containsTwoStores()) { result.getThenStore().insertValue(target, newAnno); result.getElseStore().insertValue(target, newAnno); } else { result.getRegularStore().insertValue(target, newAnno); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy