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

framework.src.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.42.0
Show newest version
package org.checkerframework.framework.flow;

import org.checkerframework.common.basetype.BaseTypeChecker;
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.InstanceOfNode;
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.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.FlowExpressionParseUtil;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionContext;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;

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 java.util.regex.Matcher;
import java.util.regex.Pattern;

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 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;

/**
 * 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. * * @author Charlie Garrett * @author Stefan Heule */ public abstract class CFAbstractTransfer, S extends CFAbstractStore, T extends CFAbstractTransfer> extends AbstractNodeVisitor, TransferInput> implements TransferFunction { /** * The analysis class this store belongs to. */ protected 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; protected static final Pattern thisPattern = Pattern.compile("^(this)$"); 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 -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; GenericAnnotatedTypeFactory> factory = analysis.atypeFactory; AnnotatedTypeMirror at = AnnotatedTypeMirror.createType(type, factory, false); at.replaceAnnotations(annotatedValue.getType().getAnnotations()); return analysis.createAbstractValue(at); } 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 (fixedInitialStore != null && underlyingAST.getKind() != Kind.LAMBDA && underlyingAST.getKind() != Kind.METHOD) return fixedInitialStore; S info = analysis.createEmptyStore(sequentialSemantics); if (underlyingAST.getKind() == Kind.METHOD) { if (fixedInitialStore != null) { // copy knowledge info = analysis.createCopiedStore(fixedInitialStore); } 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()); } } return info; } else if (underlyingAST.getKind() == Kind.LAMBDA) { // Create a copy and keep only the field values (nothing else applies). info = analysis.createCopiedStore(fixedInitialStore); 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 = InternalUtils.symbol(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 = InternalUtils.symbol(loopTree.getLeaf()); if (sym != null) { anEnclosingElement = sym; break; } loopTree = loopTree.getParentPath(); } while (anEnclosingElement != null && !anEnclosingElement.equals(InternalUtils.symbol(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); } } 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 = InternalUtils.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); for (Tree member : classTree.getMembers()) { if (member instanceof VariableTree) { VariableTree vt = (VariableTree) member; final VariableElement element = TreeUtils.elementFromDeclaration(vt); AnnotatedTypeMirror type = factory.getAnnotatedType(element); 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 (isNotFullyInitializedReceiver) { // if we are in a constructor (or another method where // the receiver might not yet be fully initialized), // 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 (Pair p : preconditions) { String expression = p.first; AnnotationMirror annotation = AnnotationUtils.fromName(analysis.getTypeFactory().getElementUtils(), p.second); // Only check if the postcondition concerns this checker if (!analysis.getTypeFactory().isSupportedQualifier(annotation)) { continue; } if (flowExprContext == null) { flowExprContext = FlowExpressionParseUtil.buildFlowExprContextForDeclaration(methodTree, method.getClassTree(), analysis.checker.getContext()); } FlowExpressions.Receiver expr = null; 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) expr = FlowExpressionParseUtil.parse(expression, flowExprContext, analysis.atypeFactory.getPath(methodTree)); info.insertValue(expr, annotation); } catch (FlowExpressionParseException e) { // report errors here analysis.checker.report(e.getResult(), methodTree); } } } /** * 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); } /** * Revert 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). * * @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); } return 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). */ 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) InternalUtils.symbol( 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 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()).first.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 = InternalUtils.symbol(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); FlowExpressionContext flowExprContext = null; for (Pair p : postconditions) { String expression = p.first; AnnotationMirror anno = AnnotationUtils.fromName(analysis.getTypeFactory().getElementUtils(), p.second); // Only check if the postcondition concerns this checker if (!analysis.getTypeFactory().isSupportedQualifier(anno)) { continue; } if (flowExprContext == null) { flowExprContext = FlowExpressionParseUtil.buildFlowExprContextForUse(n, analysis.checker.getContext()); } try { FlowExpressions.Receiver r = null; String s = expression.trim(); Matcher selfMatcher = thisPattern.matcher(s); if (selfMatcher.matches()) { s = flowExprContext.receiver.toString(); // it is possible that s == "this" after this call if (flowExprContext.receiver instanceof FieldAccess) { // This changes the receiver from the one expressed in the postcondition // declaration to the actual receiver at the site of the postcondition evaluation. // For example, it will ensure that in the call to myLock.lock(), // the receiver is myLock (and not the instance of foo): // public class ReentrantLock { // @EnsuresLockHeld("this") // void lock(); // } // public class foo { // ReentrantLock myLock = new ReentrantLock(); // void lockTheLock() { // myLock.lock(); // } // } FieldAccess foo = ((FieldAccess) flowExprContext.receiver); Receiver bar = foo.getReceiver(); flowExprContext = flowExprContext.changeReceiver(bar); } } Tree methodDecl = flowExprContext.checkerContext.getTreeUtils().getTree(methodElement); /*TODO: This just preserve the old behavior in the cases we don't have the tree *TODO: (i.e. in byte code and different compilation units). The symbols *TODO: should instead be searched for in the element API (in fact for both cases *TODO: we likely want to do this) */ if (methodDecl == null) { r = FlowExpressionParseUtil.parse( s, flowExprContext, analysis.atypeFactory.getPath(tree)); } else { r = FlowExpressionParseUtil.parse( s, flowExprContext, analysis.atypeFactory.getPath(methodDecl)); } store.insertValue(r, anno); } catch (FlowExpressionParseException e) { // these errors are reported at the declaration, ignore here } } } /** * 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); FlowExpressionContext flowExprContext = null; for (Pair> p : conditionalPostconditions) { String expression = p.first; AnnotationMirror anno = AnnotationUtils.fromName(analysis .getTypeFactory().getElementUtils(), p.second.second); boolean result = p.second.first; // Only check if the postcondition concerns this checker if (!analysis.getTypeFactory().isSupportedQualifier(anno)) { continue; } if (flowExprContext == null) { flowExprContext = FlowExpressionParseUtil .buildFlowExprContextForUse(n, analysis.checker.getContext()); } try { FlowExpressions.Receiver r = null; String s = expression.trim(); Matcher selfMatcher = thisPattern.matcher(s); if (selfMatcher.matches()) { s = flowExprContext.receiver.toString(); // it is possible that s == "this" after this call if (flowExprContext.receiver instanceof FieldAccess) { // This changes the receiver from the one expressed in the postcondition // declaration to the actual receiver at the site of the postcondition evaluation. // For example, it will ensure that in the call to myLock.tryLock(), // the receiver is myLock (and not the instance of foo): // public class ReentrantLock { // @EnsuresLockHeldIf(expression="this", result=true) // boolean tryLock(); // } // public class foo { // ReentrantLock myLock = new ReentrantLock(); // boolean tryToLockTheLock() { // return myLock.tryLock(); // } // } flowExprContext = flowExprContext.changeReceiver(((FieldAccess) flowExprContext.receiver).getReceiver()); } } r = FlowExpressionParseUtil.parse( s, flowExprContext, analysis.atypeFactory.getPath(tree)); if (result) { thenStore.insertValue(r, anno); } else { elseStore.insertValue(r, anno); } } catch (FlowExpressionParseException e) { // these errors are reported at the declaration, ignore here } } } /** * 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(); return new RegularTransferResult<>(finishValue(null, store), store); } /** * 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; } /** * @see CFAbstractAnalysis#getTypeFactoryOfSubchecker(Class) */ public , U extends BaseTypeChecker> W getTypeFactoryOfSubchecker(Class checkerClass) { return analysis.getTypeFactoryOfSubchecker(checkerClass); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy