| 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.Tree; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.TreePath; |
| import com.sun.tools.javac.code.Symbol.ClassSymbol; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| 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.VariableElement; |
| import javax.lang.model.type.TypeMirror; |
| import org.checkerframework.checker.interning.qual.InternedDistinct; |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| 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.UnderlyingAST.Kind; |
| 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.EqualToNode; |
| 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.StringConcatenateAssignmentNode; |
| import org.checkerframework.dataflow.cfg.node.StringConversionNode; |
| 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.ClassName; |
| import org.checkerframework.dataflow.expression.FieldAccess; |
| import org.checkerframework.dataflow.expression.JavaExpression; |
| import org.checkerframework.dataflow.expression.LocalVariable; |
| import org.checkerframework.dataflow.expression.ThisReference; |
| import org.checkerframework.dataflow.util.NodeUtils; |
| import org.checkerframework.framework.type.AnnotatedTypeFactory; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; |
| import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; |
| import org.checkerframework.framework.util.AnnotatedTypes; |
| 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.ElementUtils; |
| import org.checkerframework.javacutil.Pair; |
| import org.checkerframework.javacutil.TreePathUtil; |
| import org.checkerframework.javacutil.TreeUtils; |
| |
| /** |
| * The default analysis transfer function for the Checker Framework propagates information through |
| * assignments and uses the {@link AnnotatedTypeFactory} to provide checker-specific logic how to |
| * combine types (e.g., what is the type of a string concatenation, given the types of the two |
| * operands) and as an abstraction function (e.g., determine the annotations on literals). |
| * |
| * <p>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<V>, |
| S extends CFAbstractStore<V, S>, |
| T extends CFAbstractTransfer<V, S, T>> |
| extends AbstractNodeVisitor<TransferResult<V, S>, TransferInput<V, S>> |
| implements ForwardTransferFunction<V, S> { |
| |
| /** The analysis used by this transfer function. */ |
| protected final CFAbstractAnalysis<V, S, T> analysis; |
| |
| /** |
| * Should the analysis use sequential Java semantics (i.e., assume that only one thread is running |
| * at all times)? |
| */ |
| protected final boolean sequentialSemantics; |
| |
| /** Indicates that the whole-program inference is on. */ |
| private final boolean infer; |
| |
| /** |
| * Create a CFAbstractTransfer. |
| * |
| * @param analysis the analysis used by this transfer function |
| */ |
| protected CFAbstractTransfer(CFAbstractAnalysis<V, S, T> 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<V, S, T> analysis, boolean forceConcurrentSemantics) { |
| this.analysis = analysis; |
| this.sequentialSemantics = |
| !(forceConcurrentSemantics || analysis.checker.hasOption("concurrentSemantics")); |
| 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 |
| */ |
| 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. |
| * |
| * <p>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 |
| */ |
| 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. |
| * |
| * <p>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 |
| */ |
| 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<V, S, T, ? extends CFAbstractAnalysis<V, S, T>> factory = |
| analysis.atypeFactory; |
| Tree preTree = analysis.getCurrentTree(); |
| Pair<Tree, AnnotatedTypeMirror> preContext = factory.getVisitorState().getAssignmentContext(); |
| analysis.setCurrentTree(tree); |
| // is there an assignment context node available? |
| if (node != null && node.getAssignmentContext() != null) { |
| // Get the declared type of the assignment context by looking up the assignment context tree's |
| // type in the factory while flow is disabled. |
| Tree contextTree = node.getAssignmentContext().getContextTree(); |
| AnnotatedTypeMirror assignmentContext = null; |
| if (contextTree != null) { |
| assignmentContext = factory.getAnnotatedTypeLhs(contextTree); |
| } else { |
| Element assignmentContextElement = node.getAssignmentContext().getElementForType(); |
| if (assignmentContextElement != null) { |
| // if contextTree is null, use the element to get the type |
| assignmentContext = factory.getAnnotatedType(assignmentContextElement); |
| } |
| } |
| |
| if (assignmentContext != null) { |
| if (assignmentContext instanceof AnnotatedExecutableType) { |
| // For a MethodReturnContext, we get the full type of the |
| // method, but we only want the return type. |
| assignmentContext = ((AnnotatedExecutableType) assignmentContext).getReturnType(); |
| } |
| factory |
| .getVisitorState() |
| .setAssignmentContext( |
| Pair.of(node.getAssignmentContext().getContextTree(), assignmentContext)); |
| } |
| } |
| 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); |
| factory.getVisitorState().setAssignmentContext(preContext); |
| return analysis.createAbstractValue(at); |
| } |
| |
| /** |
| * Returns an abstract value with the given {@code type} and the annotations from {@code |
| * annotatedValue}. |
| * |
| * @param type the type to return |
| * @param annotatedValue the annotations to return |
| * @return an abstract value with the given {@code type} and the annotations from {@code |
| * annotatedValue} |
| * @deprecated use {@link #getWidenedValue} or {@link #getNarrowedValue} |
| */ |
| @Deprecated // 2020-10-02 |
| protected V getValueWithSameAnnotations(TypeMirror type, V annotatedValue) { |
| if (annotatedValue == null) { |
| return null; |
| } |
| return analysis.createAbstractValue(annotatedValue.getAnnotations(), type); |
| } |
| |
| /** The fixed initial store. */ |
| private S fixedInitialStore = null; |
| |
| /** Set a fixed initial Store. */ |
| public void setFixedInitialStore(S s) { |
| fixedInitialStore = s; |
| } |
| |
| /** The initial store maps method formal parameters to their currently most refined type. */ |
| @Override |
| public S initialStore(UnderlyingAST underlyingAST, @Nullable List<LocalVariableNode> parameters) { |
| if (underlyingAST.getKind() != Kind.LAMBDA && underlyingAST.getKind() != Kind.METHOD) { |
| if (fixedInitialStore != null) { |
| return fixedInitialStore; |
| } else { |
| return analysis.createEmptyStore(sequentialSemantics); |
| } |
| } |
| |
| S info; |
| |
| if (underlyingAST.getKind() == Kind.METHOD) { |
| |
| if (fixedInitialStore != null) { |
| // copy knowledge |
| info = analysis.createCopiedStore(fixedInitialStore); |
| } else { |
| info = analysis.createEmptyStore(sequentialSemantics); |
| } |
| |
| AnnotatedTypeFactory factory = analysis.getTypeFactory(); |
| for (LocalVariableNode p : parameters) { |
| AnnotatedTypeMirror anno = factory.getAnnotatedType(p.getElement()); |
| info.initializeMethodParameter(p, analysis.createAbstractValue(anno)); |
| } |
| |
| // add properties known through precondition |
| CFGMethod method = (CFGMethod) underlyingAST; |
| MethodTree methodDeclTree = method.getMethod(); |
| ExecutableElement methodElem = TreeUtils.elementFromDeclaration(methodDeclTree); |
| addInformationFromPreconditions(info, factory, method, methodDeclTree, methodElem); |
| |
| final ClassTree classTree = method.getClassTree(); |
| addFieldValues(info, factory, classTree, methodDeclTree); |
| |
| addFinalLocalValues(info, methodElem); |
| |
| if (shouldPerformWholeProgramInference(methodDeclTree, methodElem)) { |
| Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods = |
| AnnotatedTypes.overriddenMethods( |
| analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem); |
| for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair : |
| overriddenMethods.entrySet()) { |
| AnnotatedExecutableType overriddenMethod = |
| AnnotatedTypes.asMemberOf( |
| analysis.atypeFactory.getProcessingEnv().getTypeUtils(), |
| analysis.atypeFactory, |
| pair.getKey(), |
| pair.getValue()); |
| |
| // Infers parameter and receiver types of the method based |
| // on the overridden method. |
| analysis |
| .atypeFactory |
| .getWholeProgramInference() |
| .updateFromOverride(methodDeclTree, methodElem, overriddenMethod); |
| } |
| } |
| |
| } else if (underlyingAST.getKind() == Kind.LAMBDA) { |
| // Create a copy and keep only the field values (nothing else applies). |
| info = 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? |
| // info.localVariableValues.clear(); |
| info.classValues.clear(); |
| info.arrayValues.clear(); |
| info.methodValues.clear(); |
| |
| AnnotatedTypeFactory factory = analysis.getTypeFactory(); |
| for (LocalVariableNode p : parameters) { |
| AnnotatedTypeMirror anno = factory.getAnnotatedType(p.getElement()); |
| info.initializeMethodParameter(p, analysis.createAbstractValue(anno)); |
| } |
| |
| CFGLambda lambda = (CFGLambda) underlyingAST; |
| @SuppressWarnings("interning:assignment") // used in == tests |
| @InternedDistinct Tree enclosingTree = |
| TreePathUtil.enclosingOfKind( |
| factory.getPath(lambda.getLambdaTree()), |
| new HashSet<>( |
| Arrays.asList( |
| Tree.Kind.METHOD, |
| // Tree.Kind for which TreeUtils.isClassTree is true |
| Tree.Kind.CLASS, |
| Tree.Kind.INTERFACE, |
| Tree.Kind.ANNOTATION_TYPE, |
| Tree.Kind.ENUM))); |
| |
| 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.elementFromTree(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 = factory.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(info, enclosingElement); |
| } |
| |
| // We want the initialization stuff, but need to throw out any refinements. |
| Map<FieldAccess, V> fieldValuesClone = new HashMap<>(info.fieldValues); |
| for (Map.Entry<FieldAccess, V> fieldValue : fieldValuesClone.entrySet()) { |
| AnnotatedTypeMirror declaredType = factory.getAnnotatedType(fieldValue.getKey().getField()); |
| V lubbedValue = |
| analysis.createAbstractValue(declaredType).leastUpperBound(fieldValue.getValue()); |
| info.fieldValues.put(fieldValue.getKey(), lubbedValue); |
| } |
| } else { |
| assert false : "Unexpected tree: " + underlyingAST; |
| info = null; |
| } |
| |
| return info; |
| } |
| |
| private void addFieldValues( |
| S info, AnnotatedTypeFactory factory, ClassTree classTree, MethodTree methodTree) { |
| |
| // Add knowledge about final fields, or values of non-final fields |
| // if we are inside a constructor (information about initializers) |
| TypeMirror classType = TreeUtils.typeOf(classTree); |
| List<Pair<VariableElement, V>> fieldValues = analysis.getFieldValues(); |
| for (Pair<VariableElement, V> p : fieldValues) { |
| VariableElement element = p.first; |
| V value = p.second; |
| if (ElementUtils.isFinal(element) || TreeUtils.isConstructor(methodTree)) { |
| JavaExpression receiver; |
| if (ElementUtils.isStatic(element)) { |
| receiver = new ClassName(classType); |
| } else { |
| receiver = new ThisReference(classType); |
| } |
| TypeMirror fieldType = ElementUtils.getType(element); |
| JavaExpression field = new FieldAccess(receiver, fieldType, element); |
| info.insertValue(field, value); |
| } |
| } |
| |
| // add properties about fields (static information from type) |
| boolean isNotFullyInitializedReceiver = isNotFullyInitializedReceiver(methodTree); |
| if (isNotFullyInitializedReceiver && !TreeUtils.isConstructor(methodTree)) { |
| // cannot add information about fields if the receiver isn't initialized |
| // and the method isn't a constructor |
| return; |
| } |
| for (Tree member : classTree.getMembers()) { |
| if (member instanceof VariableTree) { |
| VariableTree vt = (VariableTree) member; |
| final VariableElement element = TreeUtils.elementFromDeclaration(vt); |
| AnnotatedTypeMirror type = |
| ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) factory).getAnnotatedTypeLhs(vt); |
| TypeMirror fieldType = ElementUtils.getType(element); |
| JavaExpression receiver; |
| if (ElementUtils.isStatic(element)) { |
| receiver = new ClassName(classType); |
| } else { |
| receiver = new ThisReference(classType); |
| } |
| V value = analysis.createAbstractValue(type); |
| if (value == null) { |
| continue; |
| } |
| if (TreeUtils.isConstructor(methodTree)) { |
| // If we are in a constructor, then we can still use the static type, but only if there is |
| // also an initializer that already does some initialization. |
| boolean found = false; |
| for (Pair<VariableElement, V> fieldValue : fieldValues) { |
| if (fieldValue.first.equals(element)) { |
| value = value.leastUpperBound(fieldValue.second); |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| // no initializer found, cannot use static type |
| continue; |
| } |
| } |
| JavaExpression field = new FieldAccess(receiver, fieldType, element); |
| info.insertValue(field, value); |
| } |
| } |
| } |
| |
| private void addFinalLocalValues(S info, Element enclosingElement) { |
| // add information about effectively final variables (from outer scopes) |
| for (Map.Entry<Element, V> e : analysis.atypeFactory.getFinalLocalValues().entrySet()) { |
| |
| Element 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); |
| info.insertValue(l, e.getValue()); |
| break; |
| } |
| |
| enclosingMethodOfCurrentMethod = enclosingMethodOfCurrentMethod.getEnclosingElement(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns true if the receiver of a method or constructor might not yet be fully initialized. |
| * |
| * @param methodDeclTree the declaration of the method or constructor |
| * @return true if the receiver of a method or constructormight not yet be fully initialized |
| */ |
| protected boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) { |
| return TreeUtils.isConstructor(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<Precondition> 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<V, S> visitNode(Node n, TransferInput<V, S> 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. |
| * |
| * <p>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 |
| */ |
| protected TransferResult<V, S> createTransferResult(@Nullable V value, TransferInput<V, S> in) { |
| if (in.containsTwoStores()) { |
| S thenStore = in.getThenStore(); |
| S elseStore = in.getElseStore(); |
| return new ConditionalTransferResult<>( |
| finishValue(value, thenStore, elseStore), thenStore, elseStore); |
| } else { |
| S info = in.getRegularStore(); |
| return new RegularTransferResult<>(finishValue(value, info), info); |
| } |
| } |
| |
| /** |
| * Creates a TransferResult just like the given one, but with the given value. |
| * |
| * <p>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 informatio |
| */ |
| protected TransferResult<V, S> recreateTransferResult( |
| @Nullable V value, TransferResult<V, S> in) { |
| if (in.containsTwoStores()) { |
| S thenStore = in.getThenStore(); |
| S elseStore = in.getElseStore(); |
| return new ConditionalTransferResult<>( |
| finishValue(value, thenStore, elseStore), thenStore, elseStore); |
| } else { |
| S info = in.getRegularStore(); |
| return new RegularTransferResult<>(finishValue(value, info), info); |
| } |
| } |
| |
| @Override |
| public TransferResult<V, S> visitClassName(ClassNameNode n, TransferInput<V, S> 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<V, S, T, ? extends CFAbstractAnalysis<V, S, T>> 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<V, S> visitFieldAccess(FieldAccessNode n, TransferInput<V, S> 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<V, S> visitArrayAccess(ArrayAccessNode n, TransferInput<V, S> 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<V, S> visitLocalVariable(LocalVariableNode n, TransferInput<V, S> 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<V, S> visitThis(ThisNode n, TransferInput<V, S> 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<V, S> visitTernaryExpression( |
| TernaryExpressionNode n, TransferInput<V, S> p) { |
| TransferResult<V, S> 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) { |
| // The resulting abstract value is the merge of the 'then' and 'else' branch. |
| resultValue = thenValue.leastUpperBound(elseValue); |
| } |
| V finishedValue = finishValue(resultValue, thenStore, elseStore); |
| return new ConditionalTransferResult<>(finishedValue, thenStore, elseStore); |
| } |
| |
| /** Reverse the role of the 'thenStore' and 'elseStore'. */ |
| @Override |
| public TransferResult<V, S> visitConditionalNot(ConditionalNotNode n, TransferInput<V, S> p) { |
| TransferResult<V, S> result = super.visitConditionalNot(n, p); |
| S thenStore = result.getThenStore(); |
| S elseStore = result.getElseStore(); |
| return new ConditionalTransferResult<>(result.getResultValue(), elseStore, thenStore); |
| } |
| |
| @Override |
| public TransferResult<V, S> visitEqualTo(EqualToNode n, TransferInput<V, S> p) { |
| TransferResult<V, S> 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<V, S> visitNotEqual(NotEqualNode n, TransferInput<V, S> p) { |
| TransferResult<V, S> 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. |
| * |
| * <p>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 null} |
| */ |
| protected TransferResult<V, S> strengthenAnnotationOfEqualTo( |
| TransferResult<V, S> 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<Node> 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, the rhs may not appear in the result, but rather its lhs and rhs may). |
| * |
| * @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) |
| */ |
| protected List<Node> splitAssignments(Node node) { |
| if (node instanceof AssignmentNode) { |
| List<Node> 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<V, S> visitAssignment(AssignmentNode n, TransferInput<V, S> in) { |
| Node lhs = n.getTarget(); |
| Node rhs = n.getExpression(); |
| |
| S info = in.getRegularStore(); |
| V rhsValue = in.getValueOfSubNode(rhs); |
| |
| 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, analysis.getContainingClass(n.getTree())); |
| } else if (lhs instanceof LocalVariableNode |
| && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.PARAMETER) { |
| // lhs is a formal parameter of some method |
| VariableElement param = (VariableElement) ((LocalVariableNode) lhs).getElement(); |
| analysis |
| .atypeFactory |
| .getWholeProgramInference() |
| .updateFromFormalParameterAssignment((LocalVariableNode) lhs, rhs, param); |
| } |
| } |
| |
| processCommonAssignment(in, lhs, rhs, info, rhsValue); |
| |
| return new RegularTransferResult<>(finishValue(rhsValue, info), info); |
| } |
| |
| @Override |
| public TransferResult<V, S> visitReturn(ReturnNode n, TransferInput<V, S> p) { |
| TransferResult<V, S> result = super.visitReturn(n, p); |
| |
| 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.elementFromTree(classTree); |
| |
| ExecutableElement methodElem = |
| TreeUtils.elementFromDeclaration(analysis.getContainingMethod(n.getTree())); |
| |
| Map<AnnotatedDeclaredType, ExecutableElement> 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<V, S> visitLambdaResultExpression( |
| LambdaResultExpressionNode n, TransferInput<V, S> in) { |
| return n.getResult().accept(this, in); |
| } |
| |
| @Override |
| public TransferResult<V, S> visitStringConcatenateAssignment( |
| StringConcatenateAssignmentNode n, TransferInput<V, S> in) { |
| // This gets the type of LHS + RHS |
| TransferResult<V, S> result = super.visitStringConcatenateAssignment(n, in); |
| Node lhs = n.getLeftOperand(); |
| Node rhs = n.getRightOperand(); |
| |
| // update the results store if the assignment target is something we can process |
| S info = result.getRegularStore(); |
| // ResultValue is the type of LHS + RHS |
| V resultValue = result.getResultValue(); |
| |
| if (lhs instanceof FieldAccessNode |
| && shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) { |
| // Updates inferred field type |
| analysis |
| .atypeFactory |
| .getWholeProgramInference() |
| .updateFromFieldAssignment( |
| (FieldAccessNode) lhs, rhs, analysis.getContainingClass(n.getTree())); |
| } |
| |
| processCommonAssignment(in, lhs, rhs, info, resultValue); |
| |
| return new RegularTransferResult<>(finishValue(resultValue, info), info); |
| } |
| |
| /** |
| * Determine abstract value of right-hand side and update the store accordingly to the assignment. |
| */ |
| protected void processCommonAssignment( |
| TransferInput<V, S> in, Node lhs, Node rhs, S info, V rhsValue) { |
| |
| // update information in the store |
| info.updateForAssignment(lhs, rhsValue); |
| } |
| |
| @Override |
| public TransferResult<V, S> visitObjectCreation(ObjectCreationNode n, TransferInput<V, S> p) { |
| if (shouldPerformWholeProgramInference(n.getTree())) { |
| ExecutableElement constructorElt = |
| analysis.getTypeFactory().constructorFromUse(n.getTree()).executableType.getElement(); |
| analysis |
| .atypeFactory |
| .getWholeProgramInference() |
| .updateFromObjectCreation(n, constructorElt, p.getRegularStore()); |
| } |
| return super.visitObjectCreation(n, p); |
| } |
| |
| @Override |
| public TransferResult<V, S> visitMethodInvocation( |
| MethodInvocationNode n, TransferInput<V, S> in) { |
| |
| S store = in.getRegularStore(); |
| ExecutableElement method = n.getTarget().getMethod(); |
| |
| // 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); |
| } |
| |
| Tree 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); |
| |
| 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); |
| } |
| |
| @Override |
| public TransferResult<V, S> visitInstanceOf(InstanceOfNode node, TransferInput<V, S> in) { |
| TransferResult<V, S> result = super.visitInstanceOf(node, in); |
| // The "reference type" is the type after "instanceof". |
| Tree refTypeTree = node.getTree().getType(); |
| if (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; |
| } |
| |
| /** |
| * 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 |
| */ |
| private boolean shouldPerformWholeProgramInference(Tree tree) { |
| return infer && (tree == null || !analysis.checker.shouldSuppressWarnings(tree, "")); |
| } |
| |
| /** |
| * 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 |
| */ |
| private 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, ""); |
| } |
| |
| /** |
| * 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 |
| */ |
| 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 |
| * @param store a store; is side-effected by this method |
| * @param methodElement the method being called |
| * @param invocationTree the tree for the method call |
| */ |
| protected void processPostconditions( |
| MethodInvocationNode invocationNode, |
| S store, |
| ExecutableElement methodElement, |
| Tree invocationTree) { |
| ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); |
| Set<Postcondition> postconditions = contractsUtils.getPostconditions(methodElement); |
| 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, |
| Tree invocationTree, |
| S thenStore, |
| S elseStore) { |
| ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod(); |
| Set<ConditionalPostcondition> 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 |
| * @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 |
| * @param postconditions the postconditions |
| */ |
| private void processPostconditionsAndConditionalPostconditions( |
| MethodInvocationNode invocationNode, |
| Tree invocationTree, |
| S thenStore, |
| S elseStore, |
| Set<? extends Contract> postconditions) { |
| |
| StringToJavaExpression stringToJavaExpr = |
| stringExpr -> |
| StringToJavaExpression.atMethodInvocation(stringExpr, invocationNode, analysis.checker); |
| 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(TreeUtils.elementFromUse(invocationNode.getTree())); |
| 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 the argument to the switch |
| * statement. |
| */ |
| @Override |
| public TransferResult<V, S> visitCase(CaseNode n, TransferInput<V, S> in) { |
| S store = in.getRegularStore(); |
| TransferResult<V, S> result = |
| new ConditionalTransferResult<>( |
| finishValue(null, store), in.getThenStore(), in.getElseStore(), false); |
| |
| V caseValue = in.getValueOfSubNode(n.getCaseOperand()); |
| AssignmentNode assign = (AssignmentNode) n.getSwitchOperand(); |
| V switchValue = store.getValue(JavaExpression.fromNode(assign.getTarget())); |
| result = |
| strengthenAnnotationOfEqualTo( |
| result, n.getCaseOperand(), assign.getExpression(), caseValue, switchValue, false); |
| |
| // Update value of switch temporary variable |
| result = |
| strengthenAnnotationOfEqualTo( |
| result, n.getCaseOperand(), assign.getTarget(), caseValue, switchValue, false); |
| return result; |
| } |
| |
| /** |
| * 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<V, S> visitTypeCast(TypeCastNode n, |
| // TransferInput<V, S> p) { |
| // TransferResult<V, S> 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. |
| */ |
| 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<V, S> visitVariableDeclaration( |
| VariableDeclarationNode n, TransferInput<V, S> p) { |
| S store = p.getRegularStore(); |
| return new RegularTransferResult<>(finishValue(null, store), store); |
| } |
| |
| @Override |
| public TransferResult<V, S> visitWideningConversion( |
| WideningConversionNode n, TransferInput<V, S> p) { |
| TransferResult<V, S> 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 |
| */ |
| protected V getNarrowedValue(TypeMirror type, V annotatedValue) { |
| if (annotatedValue == null) { |
| return null; |
| } |
| Set<AnnotationMirror> 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 |
| */ |
| protected V getWidenedValue(TypeMirror type, V annotatedValue) { |
| if (annotatedValue == null) { |
| return null; |
| } |
| Set<AnnotationMirror> widenedAnnos = |
| analysis.atypeFactory.getWidenedAnnotations( |
| annotatedValue.getAnnotations(), |
| annotatedValue.getUnderlyingType().getKind(), |
| type.getKind()); |
| |
| return analysis.createAbstractValue(widenedAnnos, type); |
| } |
| |
| @Override |
| public TransferResult<V, S> visitNarrowingConversion( |
| NarrowingConversionNode n, TransferInput<V, S> p) { |
| TransferResult<V, S> 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<V, S> visitStringConversion(StringConversionNode n, TransferInput<V, S> p) { |
| TransferResult<V, S> result = super.visitStringConversion(n, p); |
| result.setResultValue(p.getValueOfSubNode(n.getOperand())); |
| return result; |
| } |
| |
| /** |
| * 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 |
| */ |
| public static void insertIntoStores( |
| TransferResult<CFValue, CFStore> result, JavaExpression target, AnnotationMirror newAnno) { |
| if (result.containsTwoStores()) { |
| result.getThenStore().insertValue(target, newAnno); |
| result.getElseStore().insertValue(target, newAnno); |
| } else { |
| result.getRegularStore().insertValue(target, newAnno); |
| } |
| } |
| } |