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

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

Go to download

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

There is a newer version: 3.0.0-b2
Show newest version
package org.checkerframework.framework.flow;

import com.sun.source.tree.ClassTree;
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.Map.Entry;
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.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.ClassName;
import org.checkerframework.dataflow.analysis.FlowExpressions.FieldAccess;
import org.checkerframework.dataflow.analysis.FlowExpressions.LocalVariable;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
import org.checkerframework.dataflow.analysis.FlowExpressions.ThisReference;
import org.checkerframework.dataflow.analysis.RegularTransferResult;
import org.checkerframework.dataflow.analysis.TransferFunction;
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.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.ThisLiteralNode;
import org.checkerframework.dataflow.cfg.node.VariableDeclarationNode;
import org.checkerframework.dataflow.cfg.node.WideningConversionNode;
import org.checkerframework.framework.source.Result;
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.ContractsUtils;
import org.checkerframework.framework.util.ContractsUtils.ConditionalPostcondition;
import org.checkerframework.framework.util.ContractsUtils.Contract;
import org.checkerframework.framework.util.ContractsUtils.Postcondition;
import org.checkerframework.framework.util.ContractsUtils.Precondition;
import org.checkerframework.framework.util.FlowExpressionParseUtil;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionContext;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Pair;
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).
 *
 * 

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 TransferFunction { /** The analysis class this store belongs to. */ 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; /** Indicates that the whole-program inference is on. */ private final boolean infer; public CFAbstractTransfer(CFAbstractAnalysis analysis) { this.analysis = analysis; this.sequentialSemantics = !analysis.checker.hasOption("concurrentSemantics"); this.infer = analysis.checker.hasOption("infer"); } /** * Constructor that allows forcing concurrent semantics to be on for this instance of * CFAbstractTransfer. * * @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. */ public CFAbstractTransfer( CFAbstractAnalysis analysis, boolean forceConcurrentSemantics) { this.analysis = analysis; this.sequentialSemantics = !(forceConcurrentSemantics || analysis.checker.hasOption("concurrentSemantics")); this.infer = analysis.checker.hasOption("infer"); } /** * @return 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. */ public boolean usesSequentialSemantics() { return sequentialSemantics; } /** * This method is called before returning the abstract value {@code value} as the result of the * transfer function. By default, the value is not changed but subclasses might decide to * implement some functionality. The store at this position is also passed. */ protected V finishValue(V value, S store) { return value; } /** * This method is called before returning the abstract value {@code value} as the result of the * transfer function. By default, the value is not changed but subclasses might decide to * implement some functionality. The store at this position is also passed (two stores, as the * result is a {@link ConditionalTransferResult}. */ protected V finishValue(V value, S thenStore, S elseStore) { return value; } /** * @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(); Pair preCtxt = 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 assCtxt = null; if (contextTree != null) { assCtxt = factory.getAnnotatedTypeLhs(contextTree); } else { Element assCtxtElement = node.getAssignmentContext().getElementForType(); if (assCtxtElement != null) { // if contextTree is null, use the element to get the type assCtxt = factory.getAnnotatedType(assCtxtElement); } } if (assCtxt != null) { if (assCtxt instanceof AnnotatedExecutableType) { // For a MethodReturnContext, we get the full type of the // method, but we only want the return type. assCtxt = ((AnnotatedExecutableType) assCtxt).getReturnType(); } factory.getVisitorState() .setAssignmentContext( Pair.of(node.getAssignmentContext().getContextTree(), assCtxt)); } } AnnotatedTypeMirror at = factory.getAnnotatedType(tree); analysis.setCurrentTree(preTree); factory.getVisitorState().setAssignmentContext(preCtxt); return analysis.createAbstractValue(at); } /** * @return an abstract value with the given {@code type} and the annotations from {@code * annotatedValue}. */ protected V getValueWithSameAnnotations(TypeMirror type, V annotatedValue) { if (annotatedValue == null) { return null; } return analysis.createAbstractValue(annotatedValue.getAnnotations(), type); } 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 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 methodTree = method.getMethod(); ExecutableElement methodElem = TreeUtils.elementFromDeclaration(methodTree); addInformationFromPreconditions(info, factory, method, methodTree, methodElem); final ClassTree classTree = method.getClassTree(); addFieldValues(info, factory, classTree, methodTree); addFinalLocalValues(info, methodElem); if (shouldPerformWholeProgramInference(methodTree, methodElem)) { Map overriddenMethods = AnnotatedTypes.overriddenMethods( analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem); for (Map.Entry 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() .updateInferredMethodParameterTypes( methodTree, methodElem, overriddenMethod, analysis.getTypeFactory()); analysis.atypeFactory .getWholeProgramInference() .updateInferredMethodReceiverType( methodTree, methodElem, overriddenMethod, analysis.getTypeFactory()); } } } 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; Tree enclosingTree = TreeUtils.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 fieldValuesClone = new HashMap<>(info.fieldValues); for (Entry 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> fieldValues = analysis.getFieldValues(); for (Pair p : fieldValues) { VariableElement element = p.first; V value = p.second; if (ElementUtils.isFinal(element) || TreeUtils.isConstructor(methodTree)) { Receiver receiver; if (ElementUtils.isStatic(element)) { receiver = new ClassName(classType); } else { receiver = new ThisReference(classType); } TypeMirror fieldType = ElementUtils.getType(element); Receiver 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); Receiver 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 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; } } Receiver 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 (Entry 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 might not yet be fully initialized. */ protected boolean isNotFullyInitializedReceiver(MethodTree methodTree) { return TreeUtils.isConstructor(methodTree); } /** * Add the information from all the preconditions of the method {@code method} with * corresponding tree {@code methodTree} to the store {@code info}. */ protected void addInformationFromPreconditions( S info, AnnotatedTypeFactory factory, CFGMethod method, MethodTree methodTree, ExecutableElement methodElement) { ContractsUtils contracts = ContractsUtils.getInstance(analysis.atypeFactory); FlowExpressionContext flowExprContext = null; Set preconditions = contracts.getPreconditions(methodElement); for (Precondition p : preconditions) { String expression = p.expression; AnnotationMirror annotation = p.annotation; if (flowExprContext == null) { flowExprContext = FlowExpressionContext.buildContextForMethodDeclaration( methodTree, method.getClassTree(), analysis.checker.getContext()); } TreePath localScope = analysis.atypeFactory.getPath(methodTree); annotation = standardizeAnnotationFromContract(annotation, flowExprContext, localScope); 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) FlowExpressions.Receiver expr = FlowExpressionParseUtil.parse( expression, flowExprContext, localScope, false); info.insertValue(expr, annotation); } catch (FlowExpressionParseException e) { // Errors are reported by BaseTypeVisitor.checkContractsAtMethodDeclaration() } } } /** Standardize a type qualifier annotation obtained from a contract. */ private AnnotationMirror standardizeAnnotationFromContract( AnnotationMirror annoFromContract, FlowExpressionContext flowExprContext, TreePath path) { // TODO: common implementation with BaseTypeVisitor.standardizeAnnotationFromContract if (analysis.dependentTypesHelper != null) { return analysis.dependentTypesHelper.standardizeAnnotation( flowExprContext, path, annoFromContract, false); // BaseTypeVisitor checks the validity of the annotaiton. Errors are reported there // when called from BaseTypeVisitor.checkContractsAtMethodDeclaration(). } else { return annoFromContract; } } /** * 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); } } 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 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); } } 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 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 visitThisLiteral(ThisLiteralNode 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); } /** The resulting abstract value is the merge of the 'then' and 'else' branch. */ @Override public TransferResult visitTernaryExpression( TernaryExpressionNode n, TransferInput p) { TransferResult result = super.visitTernaryExpression(n, p); S store = result.getRegularStore(); V thenValue = p.getValueOfSubNode(n.getThenOperand()); V elseValue = p.getValueOfSubNode(n.getElseOperand()); V resultValue = null; if (thenValue != null && elseValue != null) { resultValue = thenValue.leastUpperBound(elseValue); } return new RegularTransferResult<>(finishValue(resultValue, store), store); } /** 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 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 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). * *

Note that when overriding this method, when a new type is inserted into the store, * splitAssignments should be called, and the new type should be inserted into the store for * each of the resulting nodes. * * @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 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) { Receiver secondInternal = FlowExpressions.internalReprOf(analysis.getTypeFactory(), secondPart); if (CFAbstractStore.canInsertReceiver(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). */ protected List splitAssignments(Node node) { if (node instanceof AssignmentNode) { List result = new ArrayList<>(); 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(); S info = in.getRegularStore(); V rhsValue = in.getValueOfSubNode(rhs); if (shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) { if (lhs instanceof FieldAccessNode) { // Updates inferred field type analysis.atypeFactory .getWholeProgramInference() .updateInferredFieldType( (FieldAccessNode) lhs, rhs, analysis.getContainingClass(n.getTree()), analysis.getTypeFactory()); } else if (lhs instanceof LocalVariableNode && ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.PARAMETER) { analysis.atypeFactory .getWholeProgramInference() .updateInferredParameterType( (LocalVariableNode) lhs, rhs, analysis.getContainingClass(n.getTree()), analysis.getContainingMethod(n.getTree()), analysis.getTypeFactory()); } } processCommonAssignment(in, lhs, rhs, info, rhsValue); return new RegularTransferResult<>(finishValue(rhsValue, info), info); } @Override public TransferResult visitReturn(ReturnNode n, TransferInput p) { if (shouldPerformWholeProgramInference(n.getTree())) { // Retrieves class containing the method ClassTree classTree = analysis.getContainingClass(n.getTree()); ClassSymbol classSymbol = (ClassSymbol) TreeUtils.elementFromTree(classTree); // Updates the inferred return type of the method analysis.atypeFactory .getWholeProgramInference() .updateInferredMethodReturnType( n, classSymbol, analysis.getContainingMethod(n.getTree()), analysis.getTypeFactory()); } return super.visitReturn(n, p); } @Override public TransferResult visitLambdaResultExpression( LambdaResultExpressionNode n, TransferInput in) { return n.getResult().accept(this, in); } @Override public TransferResult visitStringConcatenateAssignment( StringConcatenateAssignmentNode n, TransferInput in) { // This gets the type of LHS + RHS TransferResult 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() .updateInferredFieldType( (FieldAccessNode) lhs, rhs, analysis.getContainingClass(n.getTree()), analysis.getTypeFactory()); } 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 in, Node lhs, Node rhs, S info, V rhsValue) { // update information in the store info.updateForAssignment(lhs, rhsValue); } @Override public TransferResult visitObjectCreation(ObjectCreationNode n, TransferInput p) { if (shouldPerformWholeProgramInference(n.getTree())) { ExecutableElement constructorElt = analysis.getTypeFactory() .constructorFromUse(n.getTree()) .executableType .getElement(); analysis.atypeFactory .getWholeProgramInference() .updateInferredConstructorParameterTypes( n, constructorElt, analysis.getTypeFactory()); } return super.visitObjectCreation(n, p); } @Override public TransferResult visitMethodInvocation( MethodInvocationNode n, TransferInput in) { S store = in.getRegularStore(); ExecutableElement method = n.getTarget().getMethod(); V factoryValue = null; Tree tree = n.getTree(); if (tree != null) { // look up the value from factory factoryValue = getValueFromFactory(tree, n); } // look up the 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, tree); S thenStore = store; S elseStore = thenStore.copy(); // add new information based on conditional postcondition processConditionalPostconditions(n, method, tree, thenStore, elseStore); if (shouldPerformWholeProgramInference(n.getTree(), method)) { // Finds the receiver's type Tree receiverTree = n.getTarget().getReceiver().getTree(); if (receiverTree == null) { // If there is no receiver, then get the class being visited. // This happens when the receiver corresponds to "this". receiverTree = analysis.getContainingClass(n.getTree()); // receiverTree could still be null after the call above. That // happens when the method is called from a static context. } // Updates the inferred parameter type of the invoked method analysis.atypeFactory .getWholeProgramInference() .updateInferredMethodParameterTypes( n, receiverTree, method, analysis.getTypeFactory()); } return new ConditionalTransferResult<>( finishValue(resValue, thenStore, elseStore), thenStore, elseStore); } /** * Returns true if whole-program inference should be performed. If the tree is in the scope of * a @SuppressWarning, then this method returns false. */ private boolean shouldPerformWholeProgramInference(Tree tree) { return infer && (tree == null || !analysis.checker.shouldSuppressWarnings(tree, null)); } /** * Returns true if whole-program inference should be performed. If the expressionTree or lhsTree * is in the scope of a @SuppressWarning, then this method returns false. */ private boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) { // Check that infer is true and the tree isn't in scope of a @SuppressWarning // before calling InternalUtils.symbol(lhs) if (!shouldPerformWholeProgramInference(expressionTree)) { return false; } Element elt = TreeUtils.elementFromTree(lhsTree); return !analysis.checker.shouldSuppressWarnings(elt, null); } /** * Returns true if whole-program inference should be performed. If the tree or element is in the * scope of a @SuppressWarning, then this method returns false. */ private boolean shouldPerformWholeProgramInference(Tree tree, Element elt) { return shouldPerformWholeProgramInference(tree) && !analysis.checker.shouldSuppressWarnings(elt, null); } /** * Add information based on all postconditions of method {@code n} with tree {@code tree} and * element {@code method} to the store {@code store}. */ protected void processPostconditions( MethodInvocationNode n, S store, ExecutableElement methodElement, Tree tree) { ContractsUtils contracts = ContractsUtils.getInstance(analysis.atypeFactory); Set postconditions = contracts.getPostconditions(methodElement); processPostconditionsAndConditionalPostconditions(n, tree, store, null, postconditions); } /** * Add information based on all conditional postconditions of method {@code n} with tree {@code * tree} and element {@code method} to the appropriate store. */ protected void processConditionalPostconditions( MethodInvocationNode n, ExecutableElement methodElement, Tree tree, S thenStore, S elseStore) { ContractsUtils contracts = ContractsUtils.getInstance(analysis.atypeFactory); Set conditionalPostconditions = contracts.getConditionalPostconditions(methodElement); processPostconditionsAndConditionalPostconditions( n, tree, thenStore, elseStore, conditionalPostconditions); } private void processPostconditionsAndConditionalPostconditions( MethodInvocationNode n, Tree tree, S thenStore, S elseStore, Set postconditions) { FlowExpressionContext flowExprContext = null; for (Contract p : postconditions) { String expression = p.expression; AnnotationMirror anno = p.annotation; if (flowExprContext == null) { flowExprContext = FlowExpressionContext.buildContextForMethodUse( n, analysis.checker.getContext()); } TreePath localScope = analysis.atypeFactory.getPath(tree); anno = standardizeAnnotationFromContract(anno, flowExprContext, localScope); try { FlowExpressions.Receiver r = FlowExpressionParseUtil.parse( expression, flowExprContext, localScope, false); if (p.kind == Contract.Kind.CONDITIONALPOSTCONDTION) { if (((ConditionalPostcondition) p).annoResult) { thenStore.insertValue(r, anno); } else { elseStore.insertValue(r, anno); } } else { thenStore.insertValue(r, anno); } } catch (FlowExpressionParseException e) { Result result; if (e.isFlowParseError()) { Object[] args = new Object[e.args.length + 1]; args[0] = ElementUtils.getSimpleName(TreeUtils.elementFromUse(n.getTree())); System.arraycopy(e.args, 0, args, 1, e.args.length); result = Result.failure("flowexpr.parse.error.postcondition", args); } else { result = e.getResult(); } // report errors here analysis.checker.report(result, tree); } } } /** * A case produces no value, but it may imply some facts about the argument to the switch * statement. */ @Override public TransferResult visitCase(CaseNode n, TransferInput in) { S store = in.getRegularStore(); TransferResult 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( FlowExpressions.internalReprOf( analysis.getTypeFactory(), 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 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. */ 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 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 = getValueWithSameAnnotations(n.getType(), operandValue); result.setResultValue(narrowedValue); return result; } @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 = getValueWithSameAnnotations(n.getType(), operandValue); result.setResultValue(widenedValue); return result; } @Override public TransferResult visitStringConversion( StringConversionNode n, TransferInput p) { TransferResult result = super.visitStringConversion(n, p); result.setResultValue(p.getValueOfSubNode(n.getOperand())); return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy