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

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

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;

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

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

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

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

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

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

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

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

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

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





© 2015 - 2024 Weber Informatics LLC | Privacy Policy