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

org.checkerframework.dataflow.cfg.builder.CFGTranslationPhaseOne Maven / Gradle / Ivy

Go to download

dataflow-shaded is a dataflow framework based on the javac compiler. It differs from the org.checkerframework:dataflow artifact in two ways. First, the packages in this artifact have been renamed to org.checkerframework.shaded.*. Second, unlike the dataflow artifact, this artifact contains the dependencies it requires.

There is a newer version: 3.42.0-eisop5
Show newest version
package org.checkerframework.dataflow.cfg.builder;

import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.BreakTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ContinueTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.EmptyStatementTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ErroneousTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.PrimitiveTypeTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.UnionTypeTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.tree.WildcardTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Type;

import org.checkerframework.checker.interning.qual.FindDistinct;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.Store.FlowRule;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
import org.checkerframework.dataflow.cfg.node.ArrayTypeNode;
import org.checkerframework.dataflow.cfg.node.AssertionErrorNode;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.BitwiseAndNode;
import org.checkerframework.dataflow.cfg.node.BitwiseComplementNode;
import org.checkerframework.dataflow.cfg.node.BitwiseOrNode;
import org.checkerframework.dataflow.cfg.node.BitwiseXorNode;
import org.checkerframework.dataflow.cfg.node.BooleanLiteralNode;
import org.checkerframework.dataflow.cfg.node.CaseNode;
import org.checkerframework.dataflow.cfg.node.CatchMarkerNode;
import org.checkerframework.dataflow.cfg.node.CharacterLiteralNode;
import org.checkerframework.dataflow.cfg.node.ClassDeclarationNode;
import org.checkerframework.dataflow.cfg.node.ClassNameNode;
import org.checkerframework.dataflow.cfg.node.ConditionalAndNode;
import org.checkerframework.dataflow.cfg.node.ConditionalNotNode;
import org.checkerframework.dataflow.cfg.node.ConditionalOrNode;
import org.checkerframework.dataflow.cfg.node.DeconstructorPatternNode;
import org.checkerframework.dataflow.cfg.node.DoubleLiteralNode;
import org.checkerframework.dataflow.cfg.node.EqualToNode;
import org.checkerframework.dataflow.cfg.node.ExplicitThisNode;
import org.checkerframework.dataflow.cfg.node.ExpressionStatementNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.FloatLiteralNode;
import org.checkerframework.dataflow.cfg.node.FloatingDivisionNode;
import org.checkerframework.dataflow.cfg.node.FloatingRemainderNode;
import org.checkerframework.dataflow.cfg.node.FunctionalInterfaceNode;
import org.checkerframework.dataflow.cfg.node.GreaterThanNode;
import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode;
import org.checkerframework.dataflow.cfg.node.ImplicitThisNode;
import org.checkerframework.dataflow.cfg.node.InstanceOfNode;
import org.checkerframework.dataflow.cfg.node.IntegerDivisionNode;
import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode;
import org.checkerframework.dataflow.cfg.node.IntegerRemainderNode;
import org.checkerframework.dataflow.cfg.node.LambdaResultExpressionNode;
import org.checkerframework.dataflow.cfg.node.LeftShiftNode;
import org.checkerframework.dataflow.cfg.node.LessThanNode;
import org.checkerframework.dataflow.cfg.node.LessThanOrEqualNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.LongLiteralNode;
import org.checkerframework.dataflow.cfg.node.MarkerNode;
import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
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.NullChkNode;
import org.checkerframework.dataflow.cfg.node.NullLiteralNode;
import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
import org.checkerframework.dataflow.cfg.node.NumericalMinusNode;
import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
import org.checkerframework.dataflow.cfg.node.NumericalPlusNode;
import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
import org.checkerframework.dataflow.cfg.node.PackageNameNode;
import org.checkerframework.dataflow.cfg.node.ParameterizedTypeNode;
import org.checkerframework.dataflow.cfg.node.PrimitiveTypeNode;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
import org.checkerframework.dataflow.cfg.node.SignedRightShiftNode;
import org.checkerframework.dataflow.cfg.node.StringConcatenateNode;
import org.checkerframework.dataflow.cfg.node.StringConversionNode;
import org.checkerframework.dataflow.cfg.node.StringLiteralNode;
import org.checkerframework.dataflow.cfg.node.SuperNode;
import org.checkerframework.dataflow.cfg.node.SwitchExpressionNode;
import org.checkerframework.dataflow.cfg.node.SynchronizedNode;
import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
import org.checkerframework.dataflow.cfg.node.ThisNode;
import org.checkerframework.dataflow.cfg.node.ThrowNode;
import org.checkerframework.dataflow.cfg.node.TypeCastNode;
import org.checkerframework.dataflow.cfg.node.UnsignedRightShiftNode;
import org.checkerframework.dataflow.cfg.node.ValueLiteralNode;
import org.checkerframework.dataflow.cfg.node.VariableDeclarationNode;
import org.checkerframework.dataflow.cfg.node.WideningConversionNode;
import org.checkerframework.dataflow.qual.AssertMethod;
import org.checkerframework.dataflow.qual.TerminatesExecution;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.SystemUtil;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.BindingPatternUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.CaseUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.DeconstructionPatternUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.InstanceOfUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.YieldUtils;
import org.checkerframework.javacutil.TypeAnnotationUtils;
import org.checkerframework.javacutil.TypeKindUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.trees.TreeBuilder;
import org.plumelib.util.ArrayMap;
import org.plumelib.util.ArraySet;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.IPair;
import org.plumelib.util.IdentityArraySet;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.ProcessingEnvironment;
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.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.ReferenceType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

/**
 * Class that performs phase one of the translation process. It generates the following information:
 *
 * 
    *
  • A sequence of extended nodes. *
  • A set of bindings from {@link Label}s to positions in the node sequence. *
  • A set of leader nodes that give rise to basic blocks in phase two. *
  • A mapping from AST tree nodes to {@link Node}s. *
* *

The return type of this scanner is {@link Node}. For expressions, the corresponding node is * returned to allow linking between different nodes. * *

However, for statements there is usually no single {@link Node} that is created, and thus null * is returned. * *

Every {@code visit*} method is assumed to add at least one extended node to the list of nodes * (which might only be a jump). * *

The entry point to process a single body (e.g., method, lambda, top-level block) is {@link * #process(TreePath, UnderlyingAST)}. */ @SuppressWarnings("nullness") // TODO public class CFGTranslationPhaseOne extends TreeScanner { /** Path to the tree currently being scanned. */ private TreePath path; /** Annotation processing environment. */ protected final ProcessingEnvironment env; /** Element utilities. */ protected final Elements elements; /** Type utilities. */ protected final Types types; /** Tree utilities. */ protected final Trees trees; /** TreeBuilder instance. */ protected final TreeBuilder treeBuilder; /** The annotation provider, e.g., a type factory. */ protected final AnnotationProvider annotationProvider; /** Can assertions be assumed to be disabled? */ protected final boolean assumeAssertionsDisabled; /** Can assertions be assumed to be enabled? */ protected final boolean assumeAssertionsEnabled; /* --------------------------------------------------------- */ /* Extended Node Types and Labels */ /* --------------------------------------------------------- */ /** Special label to identify the regular exit. */ private final Label regularExitLabel; /** Special label to identify the exceptional exit. */ private final Label exceptionalExitLabel; /** * Current {@link LabelCell} to which a return statement should jump, or null if there is no * valid destination. */ private @Nullable LabelCell returnTargetLC; /** * Current {@link LabelCell} to which a break statement with no label should jump, or null if * there is no valid destination. */ private @Nullable LabelCell breakTargetLC; /** * Map from AST label Names to CFG {@link Label}s for breaks. Each labeled statement creates two * CFG {@link Label}s, one for break and one for continue. */ private Map breakLabels; /** * Current {@link LabelCell} to which a continue statement with no label should jump, or null if * there is no valid destination. */ private @Nullable LabelCell continueTargetLC; /** * Map from AST label Names to CFG {@link Label}s for continues. Each labeled statement creates * two CFG {@link Label}s, one for break and one for continue. */ private Map continueLabels; /** Nested scopes of try-catch blocks in force at the current program point. */ private final TryStack tryStack; /** * SwitchBuilder for the current switch. Used to match yield statements to enclosing switches. */ private SwitchBuilder switchBuilder; /** * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will * have at least one corresponding Node. Trees that undergo conversions, such as boxing or * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in * the treeToCfgNodes, while the Node for the post-conversion value is stored in the * treeToConvertedCfgNodes. */ private final IdentityHashMap> treeToCfgNodes; /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ private final IdentityHashMap> treeToConvertedCfgNodes; /** * Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the * synthetic tree that is {@code v + 1} or {@code v - 1}. */ private final IdentityHashMap postfixTreeToCfgNodes; /** The list of extended nodes. */ private final ArrayList nodeList; /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ private final Map bindings; /** The set of leaders (represented as indices into {@code nodeList}). */ private final Set leaders; /** * All return nodes (if any) encountered. Only includes return statements that actually return * something. */ private final List returnNodes; /** * Class declarations that have been encountered when building the control-flow graph for a * method. */ private final List declaredClasses; /** * Lambdas encountered when building the control-flow graph for a method, variable initializer, * or initializer. */ private final List declaredLambdas; /** The ArithmeticException type. */ protected final TypeMirror arithmeticExceptionType; /** The ArrayIndexOutOfBoundsException type. */ protected final TypeMirror arrayIndexOutOfBoundsExceptionType; /** The AssertionError type. */ protected final TypeMirror assertionErrorType; /** The ClassCastException type . */ protected final TypeMirror classCastExceptionType; /** The Iterable type (erased). */ protected final TypeMirror iterableType; /** The NegativeArraySizeException type. */ protected final TypeMirror negativeArraySizeExceptionType; /** The NullPointerException type . */ protected final TypeMirror nullPointerExceptionType; /** The OutOfMemoryError type. */ protected final @Nullable TypeMirror outOfMemoryErrorType; /** The ClassCircularityError type. */ protected final @Nullable TypeMirror classCircularityErrorType; /** The ClassFormatErrorType type. */ protected final @Nullable TypeMirror classFormatErrorType; /** The NoClassDefFoundError type. */ protected final @Nullable TypeMirror noClassDefFoundErrorType; /** The String type. */ protected final TypeMirror stringType; /** The Throwable type. */ protected final TypeMirror throwableType; /** * Supertypes of all unchecked exceptions. The size is 2 and the contents are {@code * RuntimeException} and {@code Error}. */ protected final Set uncheckedExceptionTypes; /** * Exceptions that can be thrown by array creation "new SomeType[]". The size is 2 and the * contents are {@code NegativeArraySizeException} and {@code OutOfMemoryError}. This list comes * from JLS 15.10.1 "Run-Time Evaluation of Array Creation Expressions". */ protected final Set newArrayExceptionTypes; /** * Creates {@link CFGTranslationPhaseOne}. * * @param treeBuilder builder for new AST nodes * @param annotationProvider extracts annotations from AST nodes * @param assumeAssertionsDisabled can assertions be assumed to be disabled? * @param assumeAssertionsEnabled can assertions be assumed to be enabled? * @param env annotation processing environment containing type utilities */ public CFGTranslationPhaseOne( TreeBuilder treeBuilder, AnnotationProvider annotationProvider, boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled, ProcessingEnvironment env) { this.env = env; this.treeBuilder = treeBuilder; this.annotationProvider = annotationProvider; assert !(assumeAssertionsDisabled && assumeAssertionsEnabled); this.assumeAssertionsEnabled = assumeAssertionsEnabled; this.assumeAssertionsDisabled = assumeAssertionsDisabled; elements = env.getElementUtils(); types = env.getTypeUtils(); trees = Trees.instance(env); // initialize lists and maps treeToCfgNodes = new IdentityHashMap<>(); treeToConvertedCfgNodes = new IdentityHashMap<>(); postfixTreeToCfgNodes = new IdentityHashMap<>(); nodeList = new ArrayList<>(); bindings = new HashMap<>(); leaders = new HashSet<>(); regularExitLabel = new Label(); exceptionalExitLabel = new Label(); tryStack = new TryStack(exceptionalExitLabel); returnTargetLC = new LabelCell(regularExitLabel); breakLabels = new HashMap<>(2); continueLabels = new HashMap<>(2); returnNodes = new ArrayList<>(); declaredClasses = new ArrayList<>(); declaredLambdas = new ArrayList<>(); arithmeticExceptionType = getTypeMirror(ArithmeticException.class); arrayIndexOutOfBoundsExceptionType = getTypeMirror(ArrayIndexOutOfBoundsException.class); assertionErrorType = getTypeMirror(AssertionError.class); classCastExceptionType = getTypeMirror(ClassCastException.class); iterableType = types.erasure(getTypeMirror(Iterable.class)); negativeArraySizeExceptionType = getTypeMirror(NegativeArraySizeException.class); nullPointerExceptionType = getTypeMirror(NullPointerException.class); outOfMemoryErrorType = maybeGetTypeMirror(OutOfMemoryError.class); classCircularityErrorType = maybeGetTypeMirror(ClassCircularityError.class); classFormatErrorType = maybeGetTypeMirror(ClassFormatError.class); noClassDefFoundErrorType = maybeGetTypeMirror(NoClassDefFoundError.class); stringType = getTypeMirror(String.class); throwableType = getTypeMirror(Throwable.class); uncheckedExceptionTypes = new ArraySet<>(2); uncheckedExceptionTypes.add(getTypeMirror(RuntimeException.class)); uncheckedExceptionTypes.add(getTypeMirror(Error.class)); newArrayExceptionTypes = new ArraySet<>(2); newArrayExceptionTypes.add(negativeArraySizeExceptionType); if (outOfMemoryErrorType != null) { newArrayExceptionTypes.add(outOfMemoryErrorType); } } /** * Performs the actual work of phase one: processing a single body (of a method, lambda, * top-level block, etc.). * * @param bodyPath path to the body of the underlying AST's method * @param underlyingAST the AST for which the CFG is to be built * @return the result of phase one */ public PhaseOneResult process(TreePath bodyPath, UnderlyingAST underlyingAST) { // Set class variables this.path = bodyPath; // Traverse AST of the method body. try { // "finally" clause is "this.path = null" Node finalNode = scan(path.getLeaf(), null); // If we are building the CFG for a lambda with a single expression as the body, then // add an extra node for the result of that lambda. if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { LambdaExpressionTree lambdaTree = ((UnderlyingAST.CFGLambda) underlyingAST).getLambdaTree(); if (lambdaTree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) { Node resultNode = new LambdaResultExpressionNode( (ExpressionTree) lambdaTree.getBody(), finalNode); extendWithNode(resultNode); } } // Add marker to indicate that the next block will be the exit block. // Note: if there is a return statement earlier in the method (which is always the case // for non-void methods), then this is not strictly necessary. However, it is also not a // problem, as it will just generate a degenerate control graph case that will be // removed in a later phase. nodeList.add(new UnconditionalJump(regularExitLabel)); return new PhaseOneResult( underlyingAST, treeToCfgNodes, treeToConvertedCfgNodes, postfixTreeToCfgNodes, nodeList, bindings, leaders, returnNodes, regularExitLabel, exceptionalExitLabel, declaredClasses, declaredLambdas, types); } finally { this.path = null; } } /** * Process a single body within {@code root}. This method does not process the entire given * CompilationUnitTree. Rather, it processes one body (of a method/lambda/etc.) within it, which * corresponds to {@code underlyingAST}. * * @param root the compilation unit * @param underlyingAST the AST corresponding to the body to process * @return a PhaseOneResult */ public PhaseOneResult process(CompilationUnitTree root, UnderlyingAST underlyingAST) { // TODO: Isn't this costly? Is there no cache we can reuse? TreePath bodyPath = trees.getPath(root, underlyingAST.getCode()); assert bodyPath != null; return process(bodyPath, underlyingAST); } /** * Perform any actions required when CFG translation creates a new Tree that is not part of the * original AST. * * @param tree the newly created Tree */ public void handleArtificialTree(Tree tree) {} /** * Returns the current path for the tree currently being scanned. * * @return the current path */ public TreePath getCurrentPath() { return path; } // TODO: remove method and instead use JCP to add version-specific methods. // Switch expressions first appeared in 12, standard in 14, so don't use 17. // TODO: look into changing back to TreePathScanner and removing path/getCurrentPath. @Override public Node scan(Tree tree, Void p) { if (tree == null) { return null; } TreePath prev = path; @SuppressWarnings("interning:not.interned") // Looking for exact match. boolean treeIsLeaf = path.getLeaf() != tree; if (treeIsLeaf) { path = new TreePath(path, tree); } try { // TODO: use JCP to add version-specific behavior if (SystemUtil.jreVersion >= 14) { // Must use String comparison to support compiling on JDK 11 and earlier. // Features added between JDK 12 and JDK 17 inclusive. switch (tree.getKind().name()) { case "BINDING_PATTERN": return visitBindingPattern17(path.getLeaf(), p); case "SWITCH_EXPRESSION": return visitSwitchExpression17(tree, p); case "YIELD": return visitYield17(tree, p); case "DECONSTRUCTION_PATTERN": return visitDeconstructionPattern21(tree, p); default: // fall through to generic behavior } } return tree.accept(this, p); } finally { path = prev; } } /** * Visit a SwitchExpressionTree. * * @param yieldTree a YieldTree, typed as Tree to be backward-compatible * @param p parameter * @return the result of visiting the switch expression tree */ public Node visitYield17(Tree yieldTree, Void p) { ExpressionTree resultExpression = YieldUtils.getValue(yieldTree); switchBuilder.buildSwitchExpressionResult(resultExpression); return null; } /** * Visit a SwitchExpressionTree * * @param switchExpressionTree a SwitchExpressionTree, typed as Tree to be backward-compatible * @param p parameter * @return the result of visiting the switch expression tree */ public Node visitSwitchExpression17(Tree switchExpressionTree, Void p) { SwitchBuilder oldSwitchBuilder = switchBuilder; switchBuilder = new SwitchBuilder(switchExpressionTree); Node result = switchBuilder.build(); switchBuilder = oldSwitchBuilder; return result; } /** * Visit a BindingPatternTree * * @param bindingPatternTree a BindingPatternTree, typed as Tree to be backward-compatible * @param p parameter * @return the result of visiting the binding pattern tree */ public Node visitBindingPattern17(Tree bindingPatternTree, Void p) { ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); TypeElement classElem = TreeUtils.elementFromDeclaration(enclosingClass); Node receiver = new ImplicitThisNode(classElem.asType()); VariableTree varTree = BindingPatternUtils.getVariable(bindingPatternTree); VariableDeclarationNode variableDeclarationNode = new VariableDeclarationNode(varTree); extendWithNode(variableDeclarationNode); LocalVariableNode varNode = new LocalVariableNode(varTree, receiver); extendWithNode(varNode); return varNode; } /** * Visit a DeconstructionPatternTree. * * @param deconstructionPatternTree a DeconstructionPatternTree, typed as Tree so the Checker * Framework compiles under JDK 20 and earlier * @param p an unused parameter * @return the result of visiting the tree */ public Node visitDeconstructionPattern21(Tree deconstructionPatternTree, Void p) { List nestedPatternTrees = DeconstructionPatternUtils.getNestedPatterns(deconstructionPatternTree); List nestedPatterns = new ArrayList<>(nestedPatternTrees.size()); for (Tree pattern : nestedPatternTrees) { nestedPatterns.add(scan(pattern, p)); } DeconstructorPatternNode dcpN = new DeconstructorPatternNode( TreeUtils.typeOf(deconstructionPatternTree), deconstructionPatternTree, nestedPatterns); extendWithNode(dcpN); return dcpN; } /* --------------------------------------------------------- */ /* Nodes and Labels Management */ /* --------------------------------------------------------- */ /** * Add a node to the lookup map if it not already present. * * @param node the node to add to the lookup map */ protected void addToLookupMap(Node node) { Tree tree = node.getTree(); if (tree == null) { return; } Set existing = treeToCfgNodes.get(tree); if (existing == null) { Set newSet = new IdentityArraySet(1); newSet.add(node); treeToCfgNodes.put(tree, newSet); } else { existing.add(node); } Tree enclosingParens = parenMapping.get(tree); while (enclosingParens != null) { Set exp = treeToCfgNodes.computeIfAbsent(enclosingParens, k -> new IdentityArraySet<>(1)); // `node` could already be in set `exp`, but it's probably as fast to just add again exp.add(node); enclosingParens = parenMapping.get(enclosingParens); } } /** * Add a node in the post-conversion lookup map. The node should refer to a Tree and that Tree * should already be in the pre-conversion lookup map. This method is used to update the * Tree-Node mapping with conversion nodes. * * @param node the node to add to the lookup map */ protected void addToConvertedLookupMap(Node node) { Tree tree = node.getTree(); addToConvertedLookupMap(tree, node); } /** * Add a node in the post-conversion lookup map. The tree argument should already be in the * pre-conversion lookup map. This method is used to update the Tree-Node mapping with * conversion nodes. * * @param tree the tree used as a key in the map * @param node the node to add to the lookup map */ protected void addToConvertedLookupMap(Tree tree, Node node) { assert tree != null; assert treeToCfgNodes.containsKey(tree); Set existing = treeToConvertedCfgNodes.get(tree); if (existing == null) { Set newSet = new IdentityArraySet<>(1); newSet.add(node); treeToConvertedCfgNodes.put(tree, newSet); } else { existing.add(node); } } /** * Extend the list of extended nodes with a node. * * @param node the node to add */ protected void extendWithNode(Node node) { addToLookupMap(node); extendWithExtendedNode(new NodeHolder(node)); } /** * Extend the list of extended nodes with a node, where {@code node} might throw the exception * {@code cause}. * * @param node the node to add * @param cause an exception that the node might throw * @return the node holder */ protected NodeWithExceptionsHolder extendWithNodeWithException(Node node, TypeMirror cause) { addToLookupMap(node); return extendWithNodeWithExceptions(node, Collections.singleton(cause)); } /** * Extend the list of extended nodes with a node, where {@code node} might throw any of the * exceptions in {@code causes}. * * @param node the node to add * @param causes set of exceptions that the node might throw * @return the node holder */ protected NodeWithExceptionsHolder extendWithNodeWithExceptions( Node node, Set causes) { addToLookupMap(node); Map> exceptions = new ArrayMap<>(causes.size()); for (TypeMirror cause : causes) { exceptions.put(cause, tryStack.possibleLabels(cause)); } NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); extendWithExtendedNode(exNode); return exNode; } /** * Extend a list of extended nodes with a ClassName node. * *

Evaluating a class literal kicks off class loading (JLS 15.8.2) which can fail and throw * one of the specified subclasses of a LinkageError or an OutOfMemoryError (JLS 12.2.1). * * @param node the ClassName node to add * @return the node holder */ protected NodeWithExceptionsHolder extendWithClassNameNode(ClassNameNode node) { Set thrownSet = new ArraySet<>(4); if (classCircularityErrorType != null) { thrownSet.add(classCircularityErrorType); } if (classFormatErrorType != null) { thrownSet.add(classFormatErrorType); } if (noClassDefFoundErrorType != null) { thrownSet.add(noClassDefFoundErrorType); } if (outOfMemoryErrorType != null) { thrownSet.add(outOfMemoryErrorType); } return extendWithNodeWithExceptions(node, thrownSet); } /** * Insert {@code node} after {@code pred} in the list of extended nodes, or append to the list * if {@code pred} is not present. * * @param node the node to add * @param pred the desired predecessor of node * @return the node holder */ protected T insertNodeAfter(T node, Node pred) { addToLookupMap(node); insertExtendedNodeAfter(new NodeHolder(node), pred); return node; } /** * Insert a {@code node} that might throw the exceptions in {@code causes} after {@code pred} in * the list of extended nodes, or append to the list if {@code pred} is not present. * * @param node the node to add * @param causes set of exceptions that the node might throw * @param pred the desired predecessor of node * @return the node holder */ protected NodeWithExceptionsHolder insertNodeWithExceptionsAfter( Node node, Set causes, Node pred) { addToLookupMap(node); Map> exceptions = new ArrayMap<>(causes.size()); for (TypeMirror cause : causes) { exceptions.put(cause, tryStack.possibleLabels(cause)); } NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); insertExtendedNodeAfter(exNode, pred); return exNode; } /** * Extend the list of extended nodes with an extended node. * * @param n the extended node */ protected void extendWithExtendedNode(ExtendedNode n) { nodeList.add(n); } /** * Insert {@code n} after the node {@code pred} in the list of extended nodes, or append {@code * n} if {@code pred} is not present. * * @param n the extended node * @param pred the desired predecessor */ @SuppressWarnings("ModifyCollectionInEnhancedForLoop") protected void insertExtendedNodeAfter(ExtendedNode n, @FindDistinct Node pred) { int index = -1; for (int i = 0; i < nodeList.size(); i++) { ExtendedNode inList = nodeList.get(i); if (inList instanceof NodeHolder || inList instanceof NodeWithExceptionsHolder) { if (inList.getNode() == pred) { index = i; break; } } } if (index != -1) { nodeList.add(index + 1, n); // update bindings for (Map.Entry e : bindings.entrySet()) { if (e.getValue() >= index + 1) { bindings.put(e.getKey(), e.getValue() + 1); } } // update leaders Set oldLeaders = new HashSet<>(leaders); leaders.clear(); for (Integer l : oldLeaders) { if (l >= index + 1) { leaders.add(l + 1); } else { leaders.add(l); } } } else { nodeList.add(n); } } /** * Add the label {@code l} to the extended node that will be placed next in the sequence. * * @param l the node to add to the forthcoming extended node */ protected void addLabelForNextNode(Label l) { if (bindings.containsKey(l)) { throw new BugInCF("bindings already contains key %s: %s", l, bindings); } leaders.add(nodeList.size()); bindings.put(l, nodeList.size()); } /* --------------------------------------------------------- */ /* Utility Methods */ /* --------------------------------------------------------- */ /** The UID for the next unique name. */ protected long uid = 0; /** * Returns a unique name starting with {@code prefix}. * * @param prefix the prefix of the unique name * @return a unique name starting with {@code prefix} */ protected String uniqueName(String prefix) { return prefix + "#num" + uid++; } /** * If the input node is an unboxed primitive type, insert a call to the appropriate valueOf * method, otherwise leave it alone. * * @param node in input node * @return a Node representing the boxed version of the input, which may simply be the input * node */ protected Node box(Node node) { // For boxing conversion, see JLS 5.1.7 if (TypesUtils.isPrimitive(node.getType())) { PrimitiveType primitive = types.getPrimitiveType(node.getType().getKind()); TypeMirror boxedType = types.getDeclaredType(types.boxedClass(primitive)); TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); IdentifierTree classTree = treeBuilder.buildClassUse(boxedElement); handleArtificialTree(classTree); // No need to handle possible errors from evaluating a class literal here // since this is synthetic code that can't fail. ClassNameNode className = new ClassNameNode(classTree); className.setInSource(false); insertNodeAfter(className, node); MemberSelectTree valueOfSelect = treeBuilder.buildValueOfMethodAccess(classTree); handleArtificialTree(valueOfSelect); MethodAccessNode valueOfAccess = new MethodAccessNode(valueOfSelect, className); valueOfAccess.setInSource(false); insertNodeAfter(valueOfAccess, className); MethodInvocationTree valueOfCall = treeBuilder.buildMethodInvocation( valueOfSelect, (ExpressionTree) node.getTree()); handleArtificialTree(valueOfCall); Node boxed = new MethodInvocationNode( valueOfCall, valueOfAccess, Collections.singletonList(node), getCurrentPath()); boxed.setInSource(false); // Add Throwable to account for unchecked exceptions addToConvertedLookupMap(node.getTree(), boxed); insertNodeWithExceptionsAfter(boxed, uncheckedExceptionTypes, valueOfAccess); return boxed; } else { return node; } } /** * If the input node is a boxed type, unbox it, otherwise leave it alone. * * @param node in input node * @return a Node representing the unboxed version of the input, which may simply be the input * node */ protected Node unbox(Node node) { if (TypesUtils.isBoxedPrimitive(node.getType())) { MemberSelectTree primValueSelect = treeBuilder.buildPrimValueMethodAccess(node.getTree()); handleArtificialTree(primValueSelect); MethodAccessNode primValueAccess = new MethodAccessNode(primValueSelect, node); primValueAccess.setInSource(false); // Method access may throw NullPointerException insertNodeWithExceptionsAfter( primValueAccess, Collections.singleton(nullPointerExceptionType), node); MethodInvocationTree primValueCall = treeBuilder.buildMethodInvocation(primValueSelect); handleArtificialTree(primValueCall); Node unboxed = new MethodInvocationNode( primValueCall, primValueAccess, Collections.emptyList(), getCurrentPath()); unboxed.setInSource(false); // Add Throwable to account for unchecked exceptions addToConvertedLookupMap(node.getTree(), unboxed); insertNodeWithExceptionsAfter(unboxed, uncheckedExceptionTypes, primValueAccess); return unboxed; } else { return node; } } private TreeInfo getTreeInfo(Tree tree) { TypeMirror type = TreeUtils.typeOf(tree); boolean boxed = TypesUtils.isBoxedPrimitive(type); TypeMirror unboxedType = boxed ? types.unboxedType(type) : type; boolean bool = TypesUtils.isBooleanType(type); boolean numeric = TypesUtils.isNumeric(unboxedType); return new TreeInfo() { @Override public boolean isNumeric() { return numeric; } @Override public boolean isBoxed() { return boxed; } @Override public boolean isBoolean() { return bool; } @Override public TypeMirror unboxedType() { return unboxedType; } }; } /** * Returns the unboxed tree if necessary, as described in JLS 5.1.8. * * @return the unboxed tree if necessary, as described in JLS 5.1.8 */ private Node unboxAsNeeded(Node node, boolean boxed) { return boxed ? unbox(node) : node; } /** * Convert the input node to String type, if it isn't already. * * @param node an input node * @return a Node with the value promoted to String, which may be the input node */ protected Node stringConversion(Node node) { // For string conversion, see JLS 5.1.11 if (!TypesUtils.isString(node.getType())) { Node converted = new StringConversionNode(node.getTree(), node, stringType); addToConvertedLookupMap(converted); insertNodeAfter(converted, node); return converted; } else { return node; } } /** * Perform unary numeric promotion on the input node. * * @param node a node producing a value of numeric primitive or boxed type * @return a Node with the value promoted to the int, long, float, or double; may return be the * input node */ protected Node unaryNumericPromotion(Node node) { // For unary numeric promotion, see JLS 5.6.1 node = unbox(node); switch (node.getType().getKind()) { case BYTE: case CHAR: case SHORT: { TypeMirror intType = types.getPrimitiveType(TypeKind.INT); Node widened = new WideningConversionNode(node.getTree(), node, intType); addToConvertedLookupMap(widened); insertNodeAfter(widened, node); return widened; } default: // Nothing to do. break; } return node; } /** * Returns true if the argument type is a numeric primitive or a boxed numeric primitive and * false otherwise. */ protected boolean isNumericOrBoxed(TypeMirror type) { if (TypesUtils.isBoxedPrimitive(type)) { type = types.unboxedType(type); } return TypesUtils.isNumeric(type); } /** * Compute the type to which two numeric types must be promoted before performing a binary * numeric operation on them. The input types must both be numeric and the output type is * primitive. * * @param left the type of the left operand * @param right the type of the right operand * @return a TypeMirror representing the binary numeric promoted type */ protected TypeMirror binaryPromotedType(TypeMirror left, TypeMirror right) { if (TypesUtils.isBoxedPrimitive(left)) { left = types.unboxedType(left); } if (TypesUtils.isBoxedPrimitive(right)) { right = types.unboxedType(right); } TypeKind promotedTypeKind = TypeKindUtils.widenedNumericType(left, right); return types.getPrimitiveType(promotedTypeKind); } /** * Perform binary numeric promotion on the input node to make it match the expression type. * * @param node a node producing a value of numeric primitive or boxed type * @param exprType the type to promote the value to * @return a Node with the value promoted to the exprType, which may be the input node */ protected Node binaryNumericPromotion(Node node, TypeMirror exprType) { // For binary numeric promotion, see JLS 5.6.2 node = unbox(node); if (!types.isSameType(node.getType(), exprType)) { Node widened = new WideningConversionNode(node.getTree(), node, exprType); addToConvertedLookupMap(widened); insertNodeAfter(widened, node); return widened; } else { return node; } } /** * Perform widening primitive conversion on the input node to make it match the destination * type. * * @param node a node producing a value of numeric primitive type * @param destType the type to widen the value to * @return a Node with the value widened to the exprType, which may be the input node */ protected Node widen(Node node, TypeMirror destType) { // For widening conversion, see JLS 5.1.2 assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) : "widening must be applied to primitive types"; if (types.isSubtype(node.getType(), destType) && !types.isSameType(node.getType(), destType)) { Node widened = new WideningConversionNode(node.getTree(), node, destType); addToConvertedLookupMap(widened); insertNodeAfter(widened, node); return widened; } else { return node; } } /** * Perform narrowing conversion on the input node to make it match the destination type. * * @param node a node producing a value of numeric primitive type * @param destType the type to narrow the value to * @return a Node with the value narrowed to the exprType, which may be the input node */ protected Node narrow(Node node, TypeMirror destType) { // For narrowing conversion, see JLS 5.1.3 assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) : "narrowing must be applied to primitive types"; if (types.isSubtype(destType, node.getType()) && !types.isSameType(destType, node.getType())) { Node narrowed = new NarrowingConversionNode(node.getTree(), node, destType); addToConvertedLookupMap(narrowed); insertNodeAfter(narrowed, node); return narrowed; } else { return node; } } /** * Perform narrowing conversion and optionally boxing conversion on the input node to make it * match the destination type. * * @param node a node producing a value of numeric primitive type * @param destType the type to narrow the value to (possibly boxed) * @return a Node with the value narrowed and boxed to the destType, which may be the input node */ protected Node narrowAndBox(Node node, TypeMirror destType) { if (TypesUtils.isBoxedPrimitive(destType)) { return box(narrow(node, types.unboxedType(destType))); } else { return narrow(node, destType); } } /** * Return whether a conversion from the type of the node to varType requires narrowing. * * @param varType the type of a variable (or general LHS) to be converted to * @param node a node whose value is being converted * @return whether this conversion requires narrowing to succeed */ protected boolean conversionRequiresNarrowing(TypeMirror varType, Node node) { // Narrowing is restricted to cases where the left hand side is byte, char, short or Byte, // Char, Short and the right hand side is a constant. TypeMirror unboxedVarType = TypesUtils.isBoxedPrimitive(varType) ? types.unboxedType(varType) : varType; TypeKind unboxedVarKind = unboxedVarType.getKind(); boolean isLeftNarrowableTo = unboxedVarKind == TypeKind.BYTE || unboxedVarKind == TypeKind.SHORT || unboxedVarKind == TypeKind.CHAR; boolean isRightConstant = node instanceof ValueLiteralNode; return isLeftNarrowableTo && isRightConstant; } /** * Assignment conversion and method invocation conversion are almost identical, except that * assignment conversion allows narrowing. We factor out the common logic here. * * @param node a Node producing a value * @param varType the type of a variable * @param contextAllowsNarrowing whether to allow narrowing (for assignment conversion) or not * (for method invocation conversion) * @return a Node with the value converted to the type of the variable, which may be the input * node itself */ protected Node commonConvert(Node node, TypeMirror varType, boolean contextAllowsNarrowing) { // For assignment conversion, see JLS 5.2 // For method invocation conversion, see JLS 5.3 // Check for identical types or "identity conversion" TypeMirror nodeType = node.getType(); boolean isSameType = types.isSameType(nodeType, varType); if (isSameType) { return node; } boolean isRightNumeric = TypesUtils.isNumeric(nodeType); boolean isRightPrimitive = TypesUtils.isPrimitive(nodeType); boolean isRightBoxed = TypesUtils.isBoxedPrimitive(nodeType); boolean isRightReference = nodeType instanceof ReferenceType; boolean isLeftNumeric = TypesUtils.isNumeric(varType); boolean isLeftPrimitive = TypesUtils.isPrimitive(varType); // boolean isLeftBoxed = TypesUtils.isBoxedPrimitive(varType); boolean isLeftReference = varType instanceof ReferenceType; boolean isSubtype = types.isSubtype(nodeType, varType); if (isRightNumeric && isLeftNumeric && isSubtype) { node = widen(node, varType); } else if (isRightReference && isLeftReference && isSubtype) { // widening reference conversion is a no-op, but if it // applies, then later conversions do not. } else if (isRightPrimitive && isLeftReference) { if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { node = narrowAndBox(node, varType); } else { node = box(node); } } else if (isRightBoxed && isLeftPrimitive) { node = unbox(node); nodeType = node.getType(); if (types.isSubtype(nodeType, varType) && !types.isSameType(nodeType, varType)) { node = widen(node, varType); } } else if (isRightPrimitive && isLeftPrimitive) { if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { node = narrow(node, varType); } } // `node` might have been re-assigned; if `nodeType` is needed, set it again. // nodeType = node.getType(); // TODO: if checkers need to know about null references of // a particular type, add logic for them here. return node; } /** * Perform assignment conversion so that it can be assigned to a variable of the given type. * * @param node a Node producing a value * @param varType the type of a variable * @return a Node with the value converted to the type of the variable, which may be the input * node itself */ protected Node assignConvert(Node node, TypeMirror varType) { return commonConvert(node, varType, true); } /** * Perform method invocation conversion so that the node can be passed as a formal parameter of * the given type. * * @param node a Node producing a value * @param formalType the type of a formal parameter * @return a Node with the value converted to the type of the formal, which may be the input * node itself */ protected Node methodInvocationConvert(Node node, TypeMirror formalType) { return commonConvert(node, formalType, false); } /** * Given a method element, its type at the call site, and a list of argument expressions, return * a list of {@link Node}s representing the arguments converted for a call of the method. This * method applies to both method invocations and constructor calls. The argument of newClassTree * is null when we visit {@link MethodInvocationTree}, and is non-null when we visit {@link * NewClassTree}. * * @param tree the invocation tree for the call * @param executable an ExecutableElement representing a method/constructor to be called * @param executableType an ExecutableType representing the type of the method/constructor call; * the type must be viewpoint-adapted to the call * @param actualExprs a List of argument expressions to a call * @param newClassTree the NewClassTree if the method is the invocation of a constructor * @return a List of {@link Node}s representing arguments after conversions required by a call * to this method */ protected List convertCallArguments( ExpressionTree tree, ExecutableElement executable, ExecutableType executableType, List actualExprs, @Nullable NewClassTree newClassTree) { // NOTE: It is important to convert one method argument before generating CFG nodes for the // next argument, since label binding expects nodes to be generated in execution order. // Therefore, this method first determines which conversions need to be applied and then // iterates over the actual arguments. List formals = executableType.getParameterTypes(); int numFormals = formals.size(); ArrayList convertedNodes = new ArrayList<>(numFormals); AssertMethodTuple assertMethodTuple = getAssertMethodTuple(executable); int numActuals = actualExprs.size(); if (executable.isVarArgs()) { // Create a new array argument if the actuals outnumber the formals, or if the last // actual is not assignable to the last formal. int lastArgIndex = numFormals - 1; TypeMirror lastParamType = formals.get(lastArgIndex); if (numActuals == numFormals && types.isAssignable( TreeUtils.typeOf(actualExprs.get(numActuals - 1)), lastParamType)) { // Normal call with no array creation, apply method // invocation conversion to all arguments. for (int i = 0; i < numActuals; i++) { Node actualVal = scan(actualExprs.get(i), null); if (i == assertMethodTuple.booleanParam) { treatMethodAsAssert( (MethodInvocationTree) tree, assertMethodTuple, actualVal); } if (actualVal == null) { throw new BugInCF( "CFGBuilder: scan returned null for %s [%s]", actualExprs.get(i), actualExprs.get(i).getClass()); } convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); } } else { assert lastParamType instanceof ArrayType : "variable argument formal must be an array"; // Handle anonymous constructors with an explicit enclosing expression. // There is a mismatch between the number of parameters and arguments // when the following conditions are met: // 1. Java version >= 11, // 2. the method is an anonymous constructor, // 3. the executable element has varargs, // 4. the constructor is invoked with an explicit enclosing expression. // In this case, the parameters have an enclosing expression as its first parameter, // while the arguments do not have such element. Hence, decrease the lastArgIndex // to organize the arguments from the correct index later. if (SystemUtil.jreVersion >= 11 && newClassTree != null && TreeUtils.isAnonymousConstructorWithExplicitEnclosingExpression( executable, newClassTree)) { lastArgIndex--; } // Apply method invocation conversion to lastArgIndex arguments and use the // remaining ones to initialize an array. for (int i = 0; i < lastArgIndex; i++) { Node actualVal = scan(actualExprs.get(i), null); if (i == assertMethodTuple.booleanParam) { treatMethodAsAssert( (MethodInvocationTree) tree, assertMethodTuple, actualVal); } convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); } TypeMirror elemType = ((ArrayType) lastParamType).getComponentType(); List inits = new ArrayList<>(numActuals - lastArgIndex); List initializers = new ArrayList<>(numActuals - lastArgIndex); for (int i = lastArgIndex; i < numActuals; i++) { inits.add(actualExprs.get(i)); Node actualVal = scan(actualExprs.get(i), null); initializers.add(assignConvert(actualVal, elemType)); } NewArrayTree wrappedVarargs = treeBuilder.buildNewArray(elemType, inits); handleArtificialTree(wrappedVarargs); Node lastArgument = new ArrayCreationNode( wrappedVarargs, lastParamType, /* dimensions= */ Collections.emptyList(), initializers); extendWithNode(lastArgument); convertedNodes.add(lastArgument); } } else { for (int i = 0; i < numActuals; i++) { Node actualVal = scan(actualExprs.get(i), null); if (i == assertMethodTuple.booleanParam) { treatMethodAsAssert((MethodInvocationTree) tree, assertMethodTuple, actualVal); } convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i))); } } return convertedNodes; } /** * Returns the AssertMethodTuple for {@code method}. If {@code method} is not an assert method, * then {@link AssertMethodTuple#NONE} is returned. * * @param method a method element that might be an assert method * @return the AssertMethodTuple for {@code method} */ protected AssertMethodTuple getAssertMethodTuple(ExecutableElement method) { AnnotationMirror assertMethodAnno = annotationProvider.getDeclAnnotation(method, AssertMethod.class); if (assertMethodAnno == null) { return AssertMethodTuple.NONE; } // Dataflow does not require checker-qual.jar to be on the users classpath, so // AnnotationUtils.getElementValue(...) cannot be used. int booleanParam = AnnotationUtils.getElementValueNotOnClasspath( assertMethodAnno, "parameter", Integer.class, 1) - 1; TypeMirror exceptionType = AnnotationUtils.getElementValueNotOnClasspath( assertMethodAnno, "value", Type.ClassType.class, (Type.ClassType) assertionErrorType); boolean isAssertFalse = AnnotationUtils.getElementValueNotOnClasspath( assertMethodAnno, "isAssertFalse", Boolean.class, false); return new AssertMethodTuple(booleanParam, exceptionType, isAssertFalse); } /** Holds the elements of an {@link AssertMethod} annotation. */ protected static class AssertMethodTuple { /** A tuple representing the lack of an {@link AssertMethodTuple}. */ protected static final AssertMethodTuple NONE = new AssertMethodTuple(-1, null, false); /** * 0-based index of the parameter of the expression that is tested by the assert method. (Or * -1 if this isn't an assert method.) */ public final int booleanParam; /** The type of the exception thrown by the assert method. */ public final TypeMirror exceptionType; /** Is this an assert false method? */ public final boolean isAssertFalse; /** * Creates an AssertMethodTuple. * * @param booleanParam 0-based index of the parameter of the expression that is tested by * the assert method * @param exceptionType the type of the exception thrown by the assert method * @param isAssertFalse is this an assert false method */ public AssertMethodTuple( int booleanParam, TypeMirror exceptionType, boolean isAssertFalse) { this.booleanParam = booleanParam; this.exceptionType = exceptionType; this.isAssertFalse = isAssertFalse; } } /** * Convert an operand of a conditional expression to the type of the whole expression. * * @param node a node occurring as the second or third operand of a conditional expression * @param destType the type to promote the value to * @return a Node with the value promoted to the destType, which may be the input node */ protected Node conditionalExprPromotion(Node node, TypeMirror destType) { // For rules on converting operands of conditional expressions, // JLS 15.25 TypeMirror nodeType = node.getType(); // If the operand is already the same type as the whole // expression, then do nothing. if (types.isSameType(nodeType, destType)) { return node; } // If the operand is a primitive and the whole expression is // boxed, then apply boxing. if (TypesUtils.isPrimitive(nodeType) && TypesUtils.isBoxedPrimitive(destType)) { return box(node); } // If the operand is byte or Byte and the whole expression is // short, then convert to short. boolean isBoxedPrimitive = TypesUtils.isBoxedPrimitive(nodeType); TypeMirror unboxedNodeType = isBoxedPrimitive ? types.unboxedType(nodeType) : nodeType; TypeMirror unboxedDestType = TypesUtils.isBoxedPrimitive(destType) ? types.unboxedType(destType) : destType; if (TypesUtils.isNumeric(unboxedNodeType) && TypesUtils.isNumeric(unboxedDestType)) { if (unboxedNodeType.getKind() == TypeKind.BYTE && destType.getKind() == TypeKind.SHORT) { if (isBoxedPrimitive) { node = unbox(node); } return widen(node, destType); } // If the operand is Byte, Short or Character and the whole expression // is the unboxed version of it, then apply unboxing. TypeKind destKind = destType.getKind(); if (destKind == TypeKind.BYTE || destKind == TypeKind.CHAR || destKind == TypeKind.SHORT) { if (isBoxedPrimitive) { return unbox(node); } else if (nodeType.getKind() == TypeKind.INT) { return narrow(node, destType); } } return binaryNumericPromotion(node, destType); } // For the final case in JLS 15.25, apply boxing but not lub. if (TypesUtils.isPrimitive(nodeType) && (destType.getKind() == TypeKind.DECLARED || destType.getKind() == TypeKind.UNION || destType.getKind() == TypeKind.INTERSECTION)) { return box(node); } return node; } /** * Returns the label {@link Name} of the leaf in the argument path, or null if the leaf is not a * labeled statement. */ protected @Nullable Name getLabel(TreePath path) { if (path.getParentPath() != null) { Tree parent = path.getParentPath().getLeaf(); if (parent.getKind() == Tree.Kind.LABELED_STATEMENT) { return ((LabeledStatementTree) parent).getLabel(); } } return null; } /* --------------------------------------------------------- */ /* Visitor Methods */ /* --------------------------------------------------------- */ @Override public Node visitAnnotatedType(AnnotatedTypeTree tree, Void p) { return scan(tree.getUnderlyingType(), p); } @Override public Node visitAnnotation(AnnotationTree tree, Void p) { throw new BugInCF("AnnotationTree is unexpected in AST to CFG translation"); } @Override public MethodInvocationNode visitMethodInvocation(MethodInvocationTree tree, Void p) { // see JLS 15.12.4 // First, compute the receiver, if any (15.12.4.1). // Second, evaluate the actual arguments, left to right and possibly some arguments are // stored into an array for varargs calls (15.12.4.2). // Third, test the receiver, if any, for nullness (15.12.4.4). // Fourth, convert the arguments to the type of the formal parameters (15.12.4.5). // Fifth, if the method is synchronized, lock the receiving object or class (15.12.4.5). ExecutableElement method = TreeUtils.elementFromUse(tree); if (method == null) { // The method wasn't found, e.g. because of a compilation error. return null; } ExpressionTree methodSelect = tree.getMethodSelect(); assert TreeUtils.isMethodAccess(methodSelect) : "Expected a method access, but got: " + methodSelect; List actualExprs = tree.getArguments(); // Look up method to invoke and possibly throw NullPointerException Node receiver = getReceiver(methodSelect); MethodAccessNode target = new MethodAccessNode(methodSelect, method, receiver); if (ElementUtils.isStatic(method) || receiver instanceof ThisNode) { // No NullPointerException can be thrown, use normal node extendWithNode(target); } else { extendWithNodeWithException(target, nullPointerExceptionType); } List arguments; if (TreeUtils.isEnumSuperCall(tree)) { // Don't convert arguments for enum super calls. The AST contains no actual arguments, // while the method element expects two arguments, leading to an exception in // convertCallArguments. // Since no actual arguments are present in the AST that is being checked, it shouldn't // cause any harm to omit the conversions. // See also BaseTypeVisitor.visitMethodInvocation and QualifierPolymorphism.annotate. arguments = Collections.emptyList(); } else { arguments = convertCallArguments( tree, method, TreeUtils.typeFromUse(tree), actualExprs, null); } // TODO: lock the receiver for synchronized methods MethodInvocationNode node = new MethodInvocationNode(tree, target, arguments, getCurrentPath()); ExtendedNode extendedNode = extendWithMethodInvocationNode(method, node); /* Check for the TerminatesExecution annotation. */ boolean terminatesExecution = annotationProvider.getDeclAnnotation(method, TerminatesExecution.class) != null; if (terminatesExecution) { extendedNode.setTerminatesExecution(true); } return node; } /** * Extends the CFG with a MethodInvocationNode, accounting for potential exceptions thrown by * the invocation. * * @param method the invoked method * @param node the invocation * @return an ExtendedNode representing the invocation and its possible thrown exceptions */ private ExtendedNode extendWithMethodInvocationNode( ExecutableElement method, MethodInvocationNode node) { List thrownTypes = method.getThrownTypes(); Set thrownSet = new LinkedHashSet<>(thrownTypes.size() + uncheckedExceptionTypes.size()); // Add exceptions explicitly mentioned in the throws clause. thrownSet.addAll(thrownTypes); // Add types to account for unchecked exceptions thrownSet.addAll(uncheckedExceptionTypes); return extendWithNodeWithExceptions(node, thrownSet); } @Override public Node visitAssert(AssertTree tree, Void p) { // see JLS 14.10 // If assertions are enabled, then we can just translate the assertion. if (assumeAssertionsEnabled || assumeAssertionsEnabledFor(tree)) { translateAssertWithAssertionsEnabled(tree); return null; } // If assertions are disabled, then nothing is executed. if (assumeAssertionsDisabled) { return null; } // Otherwise, we don't know if assertions are enabled, so we use a // variable "ea" and case-split on it. One branch does execute the // assertion, while the other assumes assertions are disabled. VariableTree ea = getAssertionsEnabledVariable(); // all necessary labels Label assertionEnabled = new Label(); Label assertionDisabled = new Label(); extendWithNode(new LocalVariableNode(ea)); extendWithExtendedNode(new ConditionalJump(assertionEnabled, assertionDisabled)); // 'then' branch (i.e. check the assertion) addLabelForNextNode(assertionEnabled); translateAssertWithAssertionsEnabled(tree); // 'else' branch addLabelForNextNode(assertionDisabled); return null; } /** * Should assertions be assumed to be executed for a given {@link AssertTree}? False by default. */ protected boolean assumeAssertionsEnabledFor(AssertTree tree) { return false; } /** The {@link VariableTree} that indicates whether assertions are enabled or not. */ protected VariableTree ea = null; /** * Get a synthetic {@link VariableTree} that indicates whether assertions are enabled or not. */ protected VariableTree getAssertionsEnabledVariable() { if (ea == null) { String name = uniqueName("assertionsEnabled"); Element owner = findOwner(); ExpressionTree initializer = null; ea = treeBuilder.buildVariableDecl( types.getPrimitiveType(TypeKind.BOOLEAN), name, owner, initializer); handleArtificialTree(ea); } return ea; } /** * Find nearest owner element (Method or Class) which holds current tree. * * @return nearest owner element of current tree */ private Element findOwner() { MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); if (enclosingMethod != null) { return TreeUtils.elementFromDeclaration(enclosingMethod); } else { ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); return TreeUtils.elementFromDeclaration(enclosingClass); } } /** * Translates an assertion statement to the correct CFG nodes. The translation assumes that * assertions are enabled. */ protected void translateAssertWithAssertionsEnabled(AssertTree tree) { // all necessary labels Label assertEnd = new Label(); Label elseEntry = new Label(); // basic block for the condition Node condition = unbox(scan(tree.getCondition(), null)); ConditionalJump cjump = new ConditionalJump(assertEnd, elseEntry); extendWithExtendedNode(cjump); // else branch Node detail = null; addLabelForNextNode(elseEntry); if (tree.getDetail() != null) { detail = scan(tree.getDetail(), null); } AssertionErrorNode assertNode = new AssertionErrorNode(tree, condition, detail, assertionErrorType); extendWithNode(assertNode); NodeWithExceptionsHolder exNode = extendWithNodeWithException( new ThrowNode(null, assertNode, env.getTypeUtils()), assertionErrorType); exNode.setTerminatesExecution(true); // then branch (nothing happens) addLabelForNextNode(assertEnd); } /** * Translates a method marked as {@link AssertMethod} into CFG nodes corresponding to an {@code * assert} statement. * * @param tree the method invocation tree for a method marked as {@link AssertMethod} * @param assertMethodTuple the assert method tuple for the method * @param condition the boolean expression node for the argument that the method tests */ protected void treatMethodAsAssert( MethodInvocationTree tree, AssertMethodTuple assertMethodTuple, Node condition) { // all necessary labels Label thenLabel = new Label(); Label elseLabel = new Label(); ConditionalJump cjump = new ConditionalJump(thenLabel, elseLabel); extendWithExtendedNode(cjump); addLabelForNextNode(assertMethodTuple.isAssertFalse ? thenLabel : elseLabel); AssertionErrorNode assertNode = new AssertionErrorNode(tree, condition, null, assertMethodTuple.exceptionType); extendWithNode(assertNode); NodeWithExceptionsHolder exNode = extendWithNodeWithException( new ThrowNode(null, assertNode, env.getTypeUtils()), assertMethodTuple.exceptionType); exNode.setTerminatesExecution(true); addLabelForNextNode(assertMethodTuple.isAssertFalse ? elseLabel : thenLabel); } @Override public Node visitAssignment(AssignmentTree tree, Void p) { // see JLS 15.26.1 AssignmentNode assignmentNode; ExpressionTree variable = tree.getVariable(); TypeMirror varType = TreeUtils.typeOf(variable); // case 1: lhs is field access if (TreeUtils.isFieldAccess(variable)) { // visit receiver Node receiver = getReceiver(variable); // visit expression Node expression = scan(tree.getExpression(), p); expression = assignConvert(expression, varType); // visit field access (throws null-pointer exception) FieldAccessNode target = new FieldAccessNode(variable, receiver); target.setLValue(); Element element = TreeUtils.elementFromUse(variable); if (ElementUtils.isStatic(element) || receiver instanceof ThisNode) { // No NullPointerException can be thrown, use normal node extendWithNode(target); } else { extendWithNodeWithException(target, nullPointerExceptionType); } // add assignment node assignmentNode = new AssignmentNode(tree, target, expression); extendWithNode(assignmentNode); } // case 2: lhs is not a field access else { Node target = scan(variable, p); target.setLValue(); assignmentNode = translateAssignment(tree, target, tree.getExpression()); } return assignmentNode; } /** Translate an assignment. */ protected AssignmentNode translateAssignment(Tree tree, Node target, ExpressionTree rhs) { Node expression = scan(rhs, null); return translateAssignment(tree, target, expression); } /** Translate an assignment where the RHS has already been scanned. */ protected AssignmentNode translateAssignment(Tree tree, Node target, Node expression) { assert tree instanceof AssignmentTree || tree instanceof VariableTree; target.setLValue(); expression = assignConvert(expression, target.getType()); AssignmentNode assignmentNode = new AssignmentNode(tree, target, expression); extendWithNode(assignmentNode); return assignmentNode; } /** * Note 1: Requires {@code tree} to be a field or method access tree. * *

Note 2: Visits the receiver and adds all necessary blocks to the CFG. * * @param tree the field or method access tree containing the receiver: one of * MethodInvocationTree, AssignmentTree, or IdentifierTree * @return the receiver of the field or method access */ private Node getReceiver(ExpressionTree tree) { assert TreeUtils.isFieldAccess(tree) || TreeUtils.isMethodAccess(tree); if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { // `tree` has an explicit receiver. MemberSelectTree mtree = (MemberSelectTree) tree; return scan(mtree.getExpression(), null); } // Access through an implicit receiver Element ele = TreeUtils.elementFromUse(tree); TypeElement declClassElem = ElementUtils.enclosingTypeElement(ele); TypeMirror declClassType = ElementUtils.getType(declClassElem); if (ElementUtils.isStatic(ele)) { ClassNameNode node = new ClassNameNode(declClassType, declClassElem); extendWithClassNameNode(node); return node; } // Access through an implicit `this` TreePath enclClassPath = TreePathUtil.pathTillClass(getCurrentPath()); ClassTree enclClassTree = (ClassTree) enclClassPath.getLeaf(); TypeElement enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree); TypeMirror enclClassType = enclClassElem.asType(); while (!TypesUtils.isErasedSubtype(enclClassType, declClassType, types)) { enclClassPath = TreePathUtil.pathTillClass(enclClassPath.getParentPath()); if (enclClassPath == null) { enclClassType = declClassType; break; } enclClassTree = (ClassTree) enclClassPath.getLeaf(); enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree); enclClassType = enclClassElem.asType(); } Node node = new ImplicitThisNode(enclClassType); extendWithNode(node); return node; } /** * Map an operation with assignment to the corresponding operation without assignment. * * @param kind a Tree.Kind representing an operation with assignment * @return the Tree.Kind for the same operation without assignment */ protected Tree.Kind withoutAssignment(Tree.Kind kind) { switch (kind) { case DIVIDE_ASSIGNMENT: return Tree.Kind.DIVIDE; case MULTIPLY_ASSIGNMENT: return Tree.Kind.MULTIPLY; case REMAINDER_ASSIGNMENT: return Tree.Kind.REMAINDER; case MINUS_ASSIGNMENT: return Tree.Kind.MINUS; case PLUS_ASSIGNMENT: return Tree.Kind.PLUS; case LEFT_SHIFT_ASSIGNMENT: return Tree.Kind.LEFT_SHIFT; case RIGHT_SHIFT_ASSIGNMENT: return Tree.Kind.RIGHT_SHIFT; case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: return Tree.Kind.UNSIGNED_RIGHT_SHIFT; case AND_ASSIGNMENT: return Tree.Kind.AND; case OR_ASSIGNMENT: return Tree.Kind.OR; case XOR_ASSIGNMENT: return Tree.Kind.XOR; default: return Tree.Kind.ERRONEOUS; } } @Override public Node visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { // According the JLS 15.26.2, E1 op= E2 is equivalent to // E1 = (T) ((E1) op (E2)), where T is the type of E1, // except that E1 is evaluated only once. // Tree.Kind kind = tree.getKind(); switch (kind) { case DIVIDE_ASSIGNMENT: case MULTIPLY_ASSIGNMENT: case REMAINDER_ASSIGNMENT: { // see JLS 15.17 and 15.26.2 Node targetLHS = scan(tree.getVariable(), p); Node value = scan(tree.getExpression(), p); TypeMirror exprType = TreeUtils.typeOf(tree); TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); TypeMirror promotedType = binaryPromotedType(leftType, rightType); Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); value = binaryNumericPromotion(value, promotedType); BinaryTree operTree = treeBuilder.buildBinary( promotedType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); handleArtificialTree(operTree); Node operNode; if (kind == Tree.Kind.MULTIPLY_ASSIGNMENT) { operNode = new NumericalMultiplicationNode(operTree, targetRHS, value); } else if (kind == Tree.Kind.DIVIDE_ASSIGNMENT) { if (TypesUtils.isIntegralPrimitive(exprType)) { operNode = new IntegerDivisionNode(operTree, targetRHS, value); extendWithNodeWithException(operNode, arithmeticExceptionType); } else { operNode = new FloatingDivisionNode(operTree, targetRHS, value); } } else { assert kind == Tree.Kind.REMAINDER_ASSIGNMENT; if (TypesUtils.isIntegralPrimitive(exprType)) { operNode = new IntegerRemainderNode(operTree, targetRHS, value); extendWithNodeWithException(operNode, arithmeticExceptionType); } else { operNode = new FloatingRemainderNode(operTree, targetRHS, value); } } extendWithNode(operNode); TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); handleArtificialTree(castTree); TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); castNode.setInSource(false); extendWithNode(castNode); AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); extendWithNode(assignNode); return assignNode; } case MINUS_ASSIGNMENT: case PLUS_ASSIGNMENT: { // see JLS 15.18 and 15.26.2 Node targetLHS = scan(tree.getVariable(), p); Node value = scan(tree.getExpression(), p); TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { assert (kind == Tree.Kind.PLUS_ASSIGNMENT); Node targetRHS = stringConversion(targetLHS); value = stringConversion(value); BinaryTree operTree = treeBuilder.buildBinary( leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); handleArtificialTree(operTree); Node operNode = new StringConcatenateNode(operTree, targetRHS, value); extendWithNode(operNode); AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, operNode); extendWithNode(assignNode); return assignNode; } else { TypeMirror promotedType = binaryPromotedType(leftType, rightType); Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); value = binaryNumericPromotion(value, promotedType); BinaryTree operTree = treeBuilder.buildBinary( promotedType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); handleArtificialTree(operTree); Node operNode; if (kind == Tree.Kind.PLUS_ASSIGNMENT) { operNode = new NumericalAdditionNode(operTree, targetRHS, value); } else { assert kind == Tree.Kind.MINUS_ASSIGNMENT; operNode = new NumericalSubtractionNode(operTree, targetRHS, value); } extendWithNode(operNode); TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); handleArtificialTree(castTree); TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); castNode.setInSource(false); extendWithNode(castNode); // Map the compound assignment tree to an assignment node, which // will have the correct type. AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); extendWithNode(assignNode); return assignNode; } } case LEFT_SHIFT_ASSIGNMENT: case RIGHT_SHIFT_ASSIGNMENT: case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: { // see JLS 15.19 and 15.26.2 Node targetLHS = scan(tree.getVariable(), p); Node value = scan(tree.getExpression(), p); TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); Node targetRHS = unaryNumericPromotion(targetLHS); value = unaryNumericPromotion(value); BinaryTree operTree = treeBuilder.buildBinary( leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); handleArtificialTree(operTree); Node operNode; if (kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT) { operNode = new LeftShiftNode(operTree, targetRHS, value); } else if (kind == Tree.Kind.RIGHT_SHIFT_ASSIGNMENT) { operNode = new SignedRightShiftNode(operTree, targetRHS, value); } else { assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT; operNode = new UnsignedRightShiftNode(operTree, targetRHS, value); } extendWithNode(operNode); TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); handleArtificialTree(castTree); TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); castNode.setInSource(false); extendWithNode(castNode); AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); extendWithNode(assignNode); return assignNode; } case AND_ASSIGNMENT: case OR_ASSIGNMENT: case XOR_ASSIGNMENT: // see JLS 15.22 Node targetLHS = scan(tree.getVariable(), p); Node value = scan(tree.getExpression(), p); TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); Node targetRHS = null; if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { TypeMirror promotedType = binaryPromotedType(leftType, rightType); targetRHS = binaryNumericPromotion(targetLHS, promotedType); value = binaryNumericPromotion(value, promotedType); } else if (TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType)) { targetRHS = unbox(targetLHS); value = unbox(value); } else { throw new BugInCF( "Both arguments to logical operation must be numeric or boolean"); } BinaryTree operTree = treeBuilder.buildBinary( leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); handleArtificialTree(operTree); Node operNode; if (kind == Tree.Kind.AND_ASSIGNMENT) { operNode = new BitwiseAndNode(operTree, targetRHS, value); } else if (kind == Tree.Kind.OR_ASSIGNMENT) { operNode = new BitwiseOrNode(operTree, targetRHS, value); } else { assert kind == Tree.Kind.XOR_ASSIGNMENT; operNode = new BitwiseXorNode(operTree, targetRHS, value); } extendWithNode(operNode); TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType); TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree); handleArtificialTree(castTree); TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types); castNode.setInSource(false); extendWithNode(castNode); AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); extendWithNode(assignNode); return assignNode; default: throw new BugInCF("unexpected compound assignment type"); } } @Override public Node visitBinary(BinaryTree tree, Void p) { // Note that for binary operations it is important to perform any required promotion on the // left operand before generating any Nodes for the right operand, because labels must be // inserted AFTER ALL preceding Nodes and BEFORE ALL following Nodes. Node r = null; Tree leftTree = tree.getLeftOperand(); Tree rightTree = tree.getRightOperand(); Tree.Kind kind = tree.getKind(); switch (kind) { case DIVIDE: case MULTIPLY: case REMAINDER: { // see JLS 15.17 TypeMirror exprType = TreeUtils.typeOf(tree); TypeMirror leftType = TreeUtils.typeOf(leftTree); TypeMirror rightType = TreeUtils.typeOf(rightTree); TypeMirror promotedType = binaryPromotedType(leftType, rightType); Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); if (kind == Tree.Kind.MULTIPLY) { r = new NumericalMultiplicationNode(tree, left, right); } else if (kind == Tree.Kind.DIVIDE) { if (TypesUtils.isIntegralPrimitive(exprType)) { r = new IntegerDivisionNode(tree, left, right); extendWithNodeWithException(r, arithmeticExceptionType); } else { r = new FloatingDivisionNode(tree, left, right); } } else { assert kind == Tree.Kind.REMAINDER; if (TypesUtils.isIntegralPrimitive(exprType)) { r = new IntegerRemainderNode(tree, left, right); extendWithNodeWithException(r, arithmeticExceptionType); } else { r = new FloatingRemainderNode(tree, left, right); } } break; } case MINUS: case PLUS: { // see JLS 15.18 // TypeMirror exprType = InternalUtils.typeOf(tree); TypeMirror leftType = TreeUtils.typeOf(leftTree); TypeMirror rightType = TreeUtils.typeOf(rightTree); if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { assert (kind == Tree.Kind.PLUS); Node left = stringConversion(scan(leftTree, p)); Node right = stringConversion(scan(rightTree, p)); r = new StringConcatenateNode(tree, left, right); } else { TypeMirror promotedType = binaryPromotedType(leftType, rightType); Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); // TODO: Decide whether to deal with floating-point value // set conversion. if (kind == Tree.Kind.PLUS) { r = new NumericalAdditionNode(tree, left, right); } else { assert kind == Tree.Kind.MINUS; r = new NumericalSubtractionNode(tree, left, right); } } break; } case LEFT_SHIFT: case RIGHT_SHIFT: case UNSIGNED_RIGHT_SHIFT: { // see JLS 15.19 Node left = unaryNumericPromotion(scan(leftTree, p)); Node right = unaryNumericPromotion(scan(rightTree, p)); if (kind == Tree.Kind.LEFT_SHIFT) { r = new LeftShiftNode(tree, left, right); } else if (kind == Tree.Kind.RIGHT_SHIFT) { r = new SignedRightShiftNode(tree, left, right); } else { assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT; r = new UnsignedRightShiftNode(tree, left, right); } break; } case GREATER_THAN: case GREATER_THAN_EQUAL: case LESS_THAN: case LESS_THAN_EQUAL: { // see JLS 15.20.1 TypeMirror leftType = TreeUtils.typeOf(leftTree); if (TypesUtils.isBoxedPrimitive(leftType)) { leftType = types.unboxedType(leftType); } TypeMirror rightType = TreeUtils.typeOf(rightTree); if (TypesUtils.isBoxedPrimitive(rightType)) { rightType = types.unboxedType(rightType); } TypeMirror promotedType = binaryPromotedType(leftType, rightType); Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); Node node; if (kind == Tree.Kind.GREATER_THAN) { node = new GreaterThanNode(tree, left, right); } else if (kind == Tree.Kind.GREATER_THAN_EQUAL) { node = new GreaterThanOrEqualNode(tree, left, right); } else if (kind == Tree.Kind.LESS_THAN) { node = new LessThanNode(tree, left, right); } else { assert kind == Tree.Kind.LESS_THAN_EQUAL; node = new LessThanOrEqualNode(tree, left, right); } extendWithNode(node); return node; } case EQUAL_TO: case NOT_EQUAL_TO: { // see JLS 15.21 TreeInfo leftInfo = getTreeInfo(leftTree); TreeInfo rightInfo = getTreeInfo(rightTree); Node left = scan(leftTree, p); Node right = scan(rightTree, p); if (leftInfo.isNumeric() && rightInfo.isNumeric() && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { // JLS 15.21.1 numerical equality TypeMirror promotedType = binaryPromotedType(leftInfo.unboxedType(), rightInfo.unboxedType()); left = binaryNumericPromotion(left, promotedType); right = binaryNumericPromotion(right, promotedType); } else if (leftInfo.isBoolean() && rightInfo.isBoolean() && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { // JSL 15.21.2 boolean equality left = unboxAsNeeded(left, leftInfo.isBoxed()); right = unboxAsNeeded(right, rightInfo.isBoxed()); } Node node; if (kind == Tree.Kind.EQUAL_TO) { node = new EqualToNode(tree, left, right); } else { assert kind == Tree.Kind.NOT_EQUAL_TO; node = new NotEqualNode(tree, left, right); } extendWithNode(node); return node; } case AND: case OR: case XOR: { // see JLS 15.22 TypeMirror leftType = TreeUtils.typeOf(leftTree); TypeMirror rightType = TreeUtils.typeOf(rightTree); boolean isBooleanOp = TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType); Node left; Node right; if (isBooleanOp) { left = unbox(scan(leftTree, p)); right = unbox(scan(rightTree, p)); } else if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { TypeMirror promotedType = binaryPromotedType(leftType, rightType); left = binaryNumericPromotion(scan(leftTree, p), promotedType); right = binaryNumericPromotion(scan(rightTree, p), promotedType); } else { left = unbox(scan(leftTree, p)); right = unbox(scan(rightTree, p)); } Node node; if (kind == Tree.Kind.AND) { node = new BitwiseAndNode(tree, left, right); } else if (kind == Tree.Kind.OR) { node = new BitwiseOrNode(tree, left, right); } else { assert kind == Tree.Kind.XOR; node = new BitwiseXorNode(tree, left, right); } extendWithNode(node); return node; } case CONDITIONAL_AND: case CONDITIONAL_OR: { // see JLS 15.23 and 15.24 // all necessary labels Label rightStartLabel = new Label(); Label shortCircuitLabel = new Label(); // left-hand side Node left = scan(leftTree, p); ConditionalJump cjump; if (kind == Tree.Kind.CONDITIONAL_AND) { cjump = new ConditionalJump(rightStartLabel, shortCircuitLabel); cjump.setFalseFlowRule(FlowRule.ELSE_TO_ELSE); } else { cjump = new ConditionalJump(shortCircuitLabel, rightStartLabel); cjump.setTrueFlowRule(FlowRule.THEN_TO_THEN); } extendWithExtendedNode(cjump); // right-hand side addLabelForNextNode(rightStartLabel); Node right = scan(rightTree, p); // conditional expression itself addLabelForNextNode(shortCircuitLabel); Node node; if (kind == Tree.Kind.CONDITIONAL_AND) { node = new ConditionalAndNode(tree, left, right); } else { node = new ConditionalOrNode(tree, left, right); } extendWithNode(node); return node; } default: throw new BugInCF("unexpected binary tree: " + kind); } assert r != null : "unexpected binary tree"; extendWithNode(r); return r; } @Override public Node visitBlock(BlockTree tree, Void p) { for (StatementTree n : tree.getStatements()) { scan(n, null); } return null; } @Override public Node visitBreak(BreakTree tree, Void p) { Name label = tree.getLabel(); if (label == null) { assert breakTargetLC != null : "no target for break statement"; extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); } else { assert breakLabels.containsKey(label); extendWithExtendedNode(new UnconditionalJump(breakLabels.get(label))); } return null; } // This visits a switch statement. // Switch expressions are visited by visitSwitchExpression17. @Override public Node visitSwitch(SwitchTree tree, Void p) { SwitchBuilder builder = new SwitchBuilder(tree); builder.build(); return null; } /** * Helper class for handling switch statements and switch expressions, including all their * substatements such as case labels. */ private class SwitchBuilder { /** * The tree for the switch statement or switch expression. Its type may be {@link * SwitchTree} (for a switch statement) or {@code SwitchExpressionTree}. */ private final Tree switchTree; /** The case trees of {@code switchTree} */ private final List caseTrees; /** * The Tree for the selector expression. * *

         *   switch ( selector expression ) { ... }
         * 
*/ private final ExpressionTree selectorExprTree; /** The labels for the case bodies. */ private final Label[] caseBodyLabels; /** * The Node for the assignment of the switch selector expression to a synthetic local * variable. */ private AssignmentNode selectorExprAssignment; /** * If {@link #switchTree} is a switch expression, then this is a result variable: the * synthetic variable that all results of {@code #switchTree} are assigned to. Otherwise, * this is null. */ private @Nullable VariableTree switchExprVarTree; /** * Construct a SwitchBuilder. * * @param switchTree a {@link SwitchTree} or a {@code SwitchExpressionTree} */ private SwitchBuilder(Tree switchTree) { this.switchTree = switchTree; if (TreeUtils.isSwitchStatement(switchTree)) { SwitchTree switchStatementTree = (SwitchTree) switchTree; this.caseTrees = switchStatementTree.getCases(); this.selectorExprTree = switchStatementTree.getExpression(); } else { this.caseTrees = SwitchExpressionUtils.getCases(switchTree); this.selectorExprTree = SwitchExpressionUtils.getExpression(switchTree); } // "+ 1" for the default case. If the switch has an explicit default case, then // the last element of the array is never used. this.caseBodyLabels = new Label[caseTrees.size() + 1]; } /** * Build up the CFG for the switchTree. * * @return if the switch is a switch expression, then a {@link SwitchExpressionNode}; * otherwise, null */ public @Nullable SwitchExpressionNode build() { LabelCell oldBreakTargetLC = breakTargetLC; breakTargetLC = new LabelCell(new Label()); int numCases = caseTrees.size(); for (int i = 0; i < numCases; ++i) { caseBodyLabels[i] = new Label(); } caseBodyLabels[numCases] = breakTargetLC.peekLabel(); buildSelector(); buildSwitchExpressionVar(); if (TreeUtils.isSwitchStatement(switchTree)) { // It's a switch statement, not a switch expression. extendWithNode( new MarkerNode( switchTree, "start of switch statement #" + TreeUtils.treeUids.get(switchTree), env.getTypeUtils())); } // JSL 14.11.2 // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11.2 // states "For compatibility reasons, switch statements that are not enhanced switch // statements are not required to be exhaustive". // Switch expressions and enhanced switch statements are exhaustive. boolean switchExprOrEnhanced = !TreeUtils.isSwitchStatement(switchTree) || TreeUtils.isEnhancedSwitchStatement((SwitchTree) switchTree); // Build CFG for the cases. int defaultIndex = -1; for (int i = 0; i < numCases; ++i) { CaseTree caseTree = caseTrees.get(i); if (CaseUtils.isDefaultCaseTree(caseTree)) { // Per the Java Language Specification, the checks of all cases must happen // before the default case, no matter where `default:` is written. Therefore, // build the default case last. defaultIndex = i; } else if (i == numCases - 1 && defaultIndex == -1) { // This is the last case, and there is no default case. // Switch expressions and enhanced switch statements are exhaustive. buildCase(caseTree, i, switchExprOrEnhanced); } else { buildCase(caseTree, i, false); } } if (defaultIndex != -1) { // The checks of all cases must happen before the default case, therefore we build // the default case last. // Fallthrough is still handled correctly with the caseBodyLabels. buildCase(caseTrees.get(defaultIndex), defaultIndex, false); } addLabelForNextNode(breakTargetLC.peekLabel()); breakTargetLC = oldBreakTargetLC; if (TreeUtils.isSwitchStatement(switchTree)) { // It's a switch statement, not a switch expression. extendWithNode( new MarkerNode( switchTree, "end of switch statement #" + TreeUtils.treeUids.get(switchTree), env.getTypeUtils())); } if (!TreeUtils.isSwitchStatement(switchTree)) { // It's a switch expression, not a switch statement. IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree); handleArtificialTree(switchExprVarUseTree); LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree); switchExprVarUseNode.setInSource(false); extendWithNode(switchExprVarUseNode); SwitchExpressionNode switchExpressionNode = new SwitchExpressionNode( TreeUtils.typeOf(switchTree), switchTree, switchExprVarUseNode); extendWithNode(switchExpressionNode); return switchExpressionNode; } else { return null; } } /** * Builds the CFG for the selector expression. It also creates a synthetic variable and * assigns the selector expression to the variable. This assignment node is stored in {@link * #selectorExprAssignment}. It can later be used to refine the selector expression in case * bodies. */ private void buildSelector() { // Create a synthetic variable to which the switch selector expression will be assigned TypeMirror selectorExprType = TreeUtils.typeOf(selectorExprTree); VariableTree selectorVarTree = treeBuilder.buildVariableDecl( selectorExprType, uniqueName("switch"), findOwner(), null); handleArtificialTree(selectorVarTree); VariableDeclarationNode selectorVarNode = new VariableDeclarationNode(selectorVarTree); selectorVarNode.setInSource(false); extendWithNode(selectorVarNode); IdentifierTree selectorVarUseTree = treeBuilder.buildVariableUse(selectorVarTree); handleArtificialTree(selectorVarUseTree); LocalVariableNode selectorVarUseNode = new LocalVariableNode(selectorVarUseTree); selectorVarUseNode.setInSource(false); extendWithNode(selectorVarUseNode); Node selectorExprNode = unbox(scan(selectorExprTree, null)); AssignmentTree assign = treeBuilder.buildAssignment(selectorVarUseTree, selectorExprTree); handleArtificialTree(assign); selectorExprAssignment = new AssignmentNode(assign, selectorVarUseNode, selectorExprNode); selectorExprAssignment.setInSource(false); extendWithNode(selectorExprAssignment); } /** * If {@link #switchTree} is a switch expression tree, this method creates a synthetic * variable whose value is the value of the switch expression. */ private void buildSwitchExpressionVar() { if (TreeUtils.isSwitchStatement(switchTree)) { // A switch statement does not have a value, so do nothing. return; } TypeMirror switchExprType = TreeUtils.typeOf(switchTree); switchExprVarTree = treeBuilder.buildVariableDecl( switchExprType, uniqueName("switchExpr"), findOwner(), null); handleArtificialTree(switchExprVarTree); VariableDeclarationNode switchExprVarNode = new VariableDeclarationNode(switchExprVarTree); switchExprVarNode.setInSource(false); extendWithNode(switchExprVarNode); } /** * Build the CFG for the given case tree. * * @param caseTree a case tree whose CFG to build * @param index the index of the case tree in {@link #caseBodyLabels} * @param isLastCaseOfExhaustive true if this is the last case of an exhaustive switch * statement, with no fallthrough to it. In other words, no test of the labels is * necessary. */ private void buildCase(CaseTree caseTree, int index, boolean isLastCaseOfExhaustive) { boolean isDefaultCase = CaseUtils.isDefaultCaseTree(caseTree); // If true, no test of labels is necessary. // Unfortunately, if isLastCaseOfExhaustive==TRUE, no flow-sensitive refinement occurs // within the body of the CaseNode. In the future, that can be performed, but it // requires addition of InfeasibleExitBlock, a new SpecialBlock in the CFG. boolean isTerminalCase = isDefaultCase || isLastCaseOfExhaustive; Label thisBodyLabel = caseBodyLabels[index]; Label nextBodyLabel = caseBodyLabels[index + 1]; // `nextCaseLabel` is not used if isTerminalCase==FALSE. Label nextCaseLabel = new Label(); // Handle the case expressions if (!isTerminalCase) { // A case expression exists, and it needs to be tested. ArrayList exprs = new ArrayList<>(); for (Tree exprTree : CaseUtils.getLabels(caseTree)) { exprs.add(scan(exprTree, null)); } ExpressionTree guardTree = CaseUtils.getGuard(caseTree); Node guard = (guardTree == null) ? null : scan(guardTree, null); CaseNode test = new CaseNode( caseTree, selectorExprAssignment, exprs, guard, env.getTypeUtils()); extendWithNode(test); extendWithExtendedNode(new ConditionalJump(thisBodyLabel, nextCaseLabel)); } // Handle the case body addLabelForNextNode(thisBodyLabel); if (caseTree.getStatements() != null) { // This is a switch labeled statement group. // A "switch labeled statement group" is a "case L:" label along with its code. // The code either ends with a "yield" statement, or it falls through. for (StatementTree stmt : caseTree.getStatements()) { scan(stmt, null); } // Handle possible fallthrough by adding jump to next body. if (!isTerminalCase) { extendWithExtendedNode(new UnconditionalJump(nextBodyLabel)); } } else { // This is either the default case or a switch labeled rule (which appears in a // switch expression). // A "switch labeled rule" is a "case L ->" label along with its code. Tree bodyTree = CaseUtils.getBody(caseTree); if (!TreeUtils.isSwitchStatement(switchTree) && bodyTree instanceof ExpressionTree) { buildSwitchExpressionResult((ExpressionTree) bodyTree); } else { scan(bodyTree, null); // Switch rules never fall through so add jump to the break target. assert breakTargetLC != null : "no target for case statement"; extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); } } if (!isTerminalCase) { addLabelForNextNode(nextCaseLabel); } } /** * Does the following for the result expression of a switch expression, {@code * resultExpression}: * *
    *
  1. Builds the CFG for the switch expression result. *
  2. Creates an assignment node for the assignment of {@code resultExpression} to {@code * switchExprVarTree}. *
  3. Adds an unconditional jump to {@link #breakTargetLC} (the end of the switch * expression). *
* * @param resultExpression the result of a switch expression; either from a yield or an * expression in a case rule */ /*package-private*/ void buildSwitchExpressionResult(ExpressionTree resultExpression) { IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree); handleArtificialTree(switchExprVarUseTree); LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree); switchExprVarUseNode.setInSource(false); extendWithNode(switchExprVarUseNode); Node resultExprNode = scan(resultExpression, null); AssignmentTree assign = treeBuilder.buildAssignment(switchExprVarUseTree, resultExpression); handleArtificialTree(assign); AssignmentNode assignmentNode = new AssignmentNode(assign, switchExprVarUseNode, resultExprNode); assignmentNode.setInSource(false); extendWithNode(assignmentNode); // Switch rules never fall through so add jump to the break target. assert breakTargetLC != null : "no target for case statement"; extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); } } @Override public Node visitCase(CaseTree tree, Void p) { // This assertion assumes that `case` appears only within a switch statement, throw new AssertionError("case visitor is implemented in SwitchBuilder"); } @Override public Node visitCatch(CatchTree tree, Void p) { scan(tree.getParameter(), p); scan(tree.getBlock(), p); return null; } // This is not invoked for top-level classes. Maybe it is, for classes defined within method // bodies. @Override public Node visitClass(ClassTree tree, Void p) { declaredClasses.add(tree); Node classbody = new ClassDeclarationNode(tree); extendWithNode(classbody); return classbody; } @Override public Node visitConditionalExpression(ConditionalExpressionTree tree, Void p) { // see JLS 15.25 TypeMirror exprType = TreeUtils.typeOf(tree); Label trueStart = new Label(); Label falseStart = new Label(); Label merge = new Label(); // create a synthetic variable for the value of the conditional expression VariableTree condExprVarTree = treeBuilder.buildVariableDecl(exprType, uniqueName("condExpr"), findOwner(), null); handleArtificialTree(condExprVarTree); VariableDeclarationNode condExprVarNode = new VariableDeclarationNode(condExprVarTree); condExprVarNode.setInSource(false); extendWithNode(condExprVarNode); Node condition = unbox(scan(tree.getCondition(), p)); ConditionalJump cjump = new ConditionalJump(trueStart, falseStart); extendWithExtendedNode(cjump); addLabelForNextNode(trueStart); ExpressionTree trueExprTree = tree.getTrueExpression(); Node trueExprNode = scan(trueExprTree, p); trueExprNode = conditionalExprPromotion(trueExprNode, exprType); extendWithAssignmentForConditionalExpr(condExprVarTree, trueExprTree, trueExprNode); extendWithExtendedNode(new UnconditionalJump(merge)); addLabelForNextNode(falseStart); ExpressionTree falseExprTree = tree.getFalseExpression(); Node falseExprNode = scan(falseExprTree, p); falseExprNode = conditionalExprPromotion(falseExprNode, exprType); extendWithAssignmentForConditionalExpr(condExprVarTree, falseExprTree, falseExprNode); extendWithExtendedNode(new UnconditionalJump(merge)); addLabelForNextNode(merge); IPair treeAndLocalVarNode = buildVarUseNode(condExprVarTree); Node node = new TernaryExpressionNode( tree, condition, trueExprNode, falseExprNode, treeAndLocalVarNode.second); extendWithNode(node); return node; } /** * Extend the CFG with an assignment for either the true or false case of a conditional * expression, assigning the value of the expression for the case to the synthetic variable for * the conditional expression * * @param condExprVarTree tree for synthetic variable for conditional expression * @param caseExprTree expression tree for the case * @param caseExprNode node for the case */ private void extendWithAssignmentForConditionalExpr( VariableTree condExprVarTree, ExpressionTree caseExprTree, Node caseExprNode) { IPair treeAndLocalVarNode = buildVarUseNode(condExprVarTree); AssignmentTree assign = treeBuilder.buildAssignment(treeAndLocalVarNode.first, caseExprTree); handleArtificialTree(assign); // Build a "synthetic" assignment node, allowing special handling in transfer functions AssignmentNode assignmentNode = new AssignmentNode(assign, treeAndLocalVarNode.second, caseExprNode, true); assignmentNode.setInSource(false); extendWithNode(assignmentNode); } /** * Build a pair of {@link IdentifierTree} and {@link LocalVariableNode} to represent a use of * some variable. Does not add the node to the CFG. * * @param varTree tree for the variable * @return a pair whose first element is the synthetic {@link IdentifierTree} for the use, and * whose second element is the {@link LocalVariableNode} representing the use */ private IPair buildVarUseNode(VariableTree varTree) { IdentifierTree condExprVarUseTree = treeBuilder.buildVariableUse(varTree); handleArtificialTree(condExprVarUseTree); LocalVariableNode condExprVarUseNode = new LocalVariableNode(condExprVarUseTree); condExprVarUseNode.setInSource(false); // Do not actually add the node to the CFG. return IPair.of(condExprVarUseTree, condExprVarUseNode); } @Override public Node visitContinue(ContinueTree tree, Void p) { Name label = tree.getLabel(); if (label == null) { assert continueTargetLC != null : "no target for continue statement"; extendWithExtendedNode(new UnconditionalJump(continueTargetLC.accessLabel())); } else { assert continueLabels.containsKey(label); extendWithExtendedNode(new UnconditionalJump(continueLabels.get(label))); } return null; } @Override public Node visitDoWhileLoop(DoWhileLoopTree tree, Void p) { Name parentLabel = getLabel(getCurrentPath()); Label loopEntry = new Label(); Label loopExit = new Label(); // If the loop is a labeled statement, then its continue target is identical for continues // with no label and continues with the loop's label. Label conditionStart; if (parentLabel != null) { conditionStart = continueLabels.get(parentLabel); } else { conditionStart = new Label(); } LabelCell oldBreakTargetLC = breakTargetLC; breakTargetLC = new LabelCell(loopExit); LabelCell oldContinueTargetLC = continueTargetLC; continueTargetLC = new LabelCell(conditionStart); // Loop body addLabelForNextNode(loopEntry); assert tree.getStatement() != null; scan(tree.getStatement(), p); // Condition addLabelForNextNode(conditionStart); assert tree.getCondition() != null; unbox(scan(tree.getCondition(), p)); ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); extendWithExtendedNode(cjump); // Loop exit addLabelForNextNode(loopExit); breakTargetLC = oldBreakTargetLC; continueTargetLC = oldContinueTargetLC; return null; } @Override public Node visitErroneous(ErroneousTree tree, Void p) { throw new BugInCF("ErroneousTree is unexpected in AST to CFG translation: " + tree); } @Override public Node visitExpressionStatement(ExpressionStatementTree tree, Void p) { ExpressionTree exprTree = tree.getExpression(); scan(exprTree, p); extendWithNode(new ExpressionStatementNode(exprTree)); return null; } @Override public Node visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { // see JLS 14.14.2 Name parentLabel = getLabel(getCurrentPath()); Label conditionStart = new Label(); Label loopEntry = new Label(); Label loopExit = new Label(); // If the loop is a labeled statement, then its continue target is identical for continues // with no label and continues with the loop's label. Label updateStart; if (parentLabel != null) { updateStart = continueLabels.get(parentLabel); } else { updateStart = new Label(); } LabelCell oldBreakTargetLC = breakTargetLC; breakTargetLC = new LabelCell(loopExit); LabelCell oldContinueTargetLC = continueTargetLC; continueTargetLC = new LabelCell(updateStart); // Distinguish loops over Iterables from loops over arrays. VariableTree variable = tree.getVariable(); VariableElement variableElement = TreeUtils.elementFromDeclaration(variable); ExpressionTree expression = tree.getExpression(); StatementTree statement = tree.getStatement(); TypeMirror exprType = TreeUtils.typeOf(expression); if (types.isSubtype(exprType, iterableType)) { // Take the upper bound of a type variable or wildcard exprType = TypesUtils.upperBound(exprType); assert (exprType instanceof DeclaredType) : "an Iterable must be a DeclaredType"; DeclaredType declaredExprType = (DeclaredType) exprType; declaredExprType.getTypeArguments(); MemberSelectTree iteratorSelect = treeBuilder.buildIteratorMethodAccess(expression); handleArtificialTree(iteratorSelect); MethodInvocationTree iteratorCall = treeBuilder.buildMethodInvocation(iteratorSelect); handleArtificialTree(iteratorCall); VariableTree iteratorVariable = createEnhancedForLoopIteratorVariable(iteratorCall, variableElement); handleArtificialTree(iteratorVariable); VariableDeclarationNode iteratorVariableDecl = new VariableDeclarationNode(iteratorVariable); iteratorVariableDecl.setInSource(false); extendWithNode(iteratorVariableDecl); Node expressionNode = scan(expression, p); MethodAccessNode iteratorAccessNode = new MethodAccessNode(iteratorSelect, expressionNode); iteratorAccessNode.setInSource(false); extendWithNode(iteratorAccessNode); MethodInvocationNode iteratorCallNode = new MethodInvocationNode( iteratorCall, iteratorAccessNode, Collections.emptyList(), getCurrentPath()); iteratorCallNode.setInSource(false); extendWithNode(iteratorCallNode); translateAssignment( iteratorVariable, new LocalVariableNode(iteratorVariable), iteratorCallNode); // Test the loop ending condition addLabelForNextNode(conditionStart); IdentifierTree iteratorUse1 = treeBuilder.buildVariableUse(iteratorVariable); handleArtificialTree(iteratorUse1); LocalVariableNode iteratorReceiverNode = new LocalVariableNode(iteratorUse1); iteratorReceiverNode.setInSource(false); extendWithNode(iteratorReceiverNode); MemberSelectTree hasNextSelect = treeBuilder.buildHasNextMethodAccess(iteratorUse1); handleArtificialTree(hasNextSelect); MethodAccessNode hasNextAccessNode = new MethodAccessNode(hasNextSelect, iteratorReceiverNode); hasNextAccessNode.setInSource(false); extendWithNode(hasNextAccessNode); MethodInvocationTree hasNextCall = treeBuilder.buildMethodInvocation(hasNextSelect); handleArtificialTree(hasNextCall); MethodInvocationNode hasNextCallNode = new MethodInvocationNode( hasNextCall, hasNextAccessNode, Collections.emptyList(), getCurrentPath()); hasNextCallNode.setInSource(false); extendWithNode(hasNextCallNode); extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); // Loop body, starting with declaration of the loop iteration variable addLabelForNextNode(loopEntry); extendWithNode(new VariableDeclarationNode(variable)); IdentifierTree iteratorUse2 = treeBuilder.buildVariableUse(iteratorVariable); handleArtificialTree(iteratorUse2); LocalVariableNode iteratorReceiverNode2 = new LocalVariableNode(iteratorUse2); iteratorReceiverNode2.setInSource(false); extendWithNode(iteratorReceiverNode2); MemberSelectTree nextSelect = treeBuilder.buildNextMethodAccess(iteratorUse2); handleArtificialTree(nextSelect); MethodAccessNode nextAccessNode = new MethodAccessNode(nextSelect, iteratorReceiverNode2); nextAccessNode.setInSource(false); extendWithNode(nextAccessNode); MethodInvocationTree nextCall = treeBuilder.buildMethodInvocation(nextSelect); handleArtificialTree(nextCall); MethodInvocationNode nextCallNode = new MethodInvocationNode( nextCall, nextAccessNode, Collections.emptyList(), getCurrentPath()); // If the type of iteratorVariable is a capture, its type tree may be missing // annotations, so save the expression in the node so that the full type can be // found later. nextCallNode.setIterableExpression(expression); nextCallNode.setInSource(false); extendWithNode(nextCallNode); AssignmentNode assignNode = translateAssignment(variable, new LocalVariableNode(variable), nextCall); // translateAssignment() scans variable and creates new nodes, so set the expression // there, too. ((MethodInvocationNode) assignNode.getExpression()).setIterableExpression(expression); assert statement != null; scan(statement, p); // Loop back edge addLabelForNextNode(updateStart); extendWithExtendedNode(new UnconditionalJump(conditionStart)); } else { // TODO: Shift any labels after the initialization of the // temporary array variable. VariableTree arrayVariable = createEnhancedForLoopArrayVariable(expression, variableElement); handleArtificialTree(arrayVariable); VariableDeclarationNode arrayVariableNode = new VariableDeclarationNode(arrayVariable); arrayVariableNode.setInSource(false); extendWithNode(arrayVariableNode); Node expressionNode = scan(expression, p); translateAssignment( arrayVariable, new LocalVariableNode(arrayVariable), expressionNode); // Declare and initialize the loop index variable TypeMirror intType = types.getPrimitiveType(TypeKind.INT); LiteralTree zero = treeBuilder.buildLiteral(Integer.valueOf(0)); handleArtificialTree(zero); VariableTree indexVariable = treeBuilder.buildVariableDecl( intType, uniqueName("index"), variableElement.getEnclosingElement(), zero); handleArtificialTree(indexVariable); VariableDeclarationNode indexVariableNode = new VariableDeclarationNode(indexVariable); indexVariableNode.setInSource(false); extendWithNode(indexVariableNode); IntegerLiteralNode zeroNode = new IntegerLiteralNode(zero); extendWithNode(zeroNode); translateAssignment(indexVariable, new LocalVariableNode(indexVariable), zeroNode); // Compare index to array length addLabelForNextNode(conditionStart); IdentifierTree indexUse1 = treeBuilder.buildVariableUse(indexVariable); handleArtificialTree(indexUse1); LocalVariableNode indexNode1 = new LocalVariableNode(indexUse1); indexNode1.setInSource(false); extendWithNode(indexNode1); IdentifierTree arrayUse1 = treeBuilder.buildVariableUse(arrayVariable); handleArtificialTree(arrayUse1); LocalVariableNode arrayNode1 = new LocalVariableNode(arrayUse1); extendWithNode(arrayNode1); MemberSelectTree lengthSelect = treeBuilder.buildArrayLengthAccess(arrayUse1); handleArtificialTree(lengthSelect); FieldAccessNode lengthAccessNode = new FieldAccessNode(lengthSelect, arrayNode1); lengthAccessNode.setInSource(false); extendWithNode(lengthAccessNode); BinaryTree lessThan = treeBuilder.buildLessThan(indexUse1, lengthSelect); handleArtificialTree(lessThan); LessThanNode lessThanNode = new LessThanNode(lessThan, indexNode1, lengthAccessNode); lessThanNode.setInSource(false); extendWithNode(lessThanNode); extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); // Loop body, starting with declaration of the loop iteration variable addLabelForNextNode(loopEntry); extendWithNode(new VariableDeclarationNode(variable)); IdentifierTree arrayUse2 = treeBuilder.buildVariableUse(arrayVariable); handleArtificialTree(arrayUse2); LocalVariableNode arrayNode2 = new LocalVariableNode(arrayUse2); arrayNode2.setInSource(false); extendWithNode(arrayNode2); IdentifierTree indexUse2 = treeBuilder.buildVariableUse(indexVariable); handleArtificialTree(indexUse2); LocalVariableNode indexNode2 = new LocalVariableNode(indexUse2); indexNode2.setInSource(false); extendWithNode(indexNode2); ArrayAccessTree arrayAccess = treeBuilder.buildArrayAccess(arrayUse2, indexUse2); handleArtificialTree(arrayAccess); ArrayAccessNode arrayAccessNode = new ArrayAccessNode(arrayAccess, arrayNode2, indexNode2); arrayAccessNode.setArrayExpression(expression); arrayAccessNode.setInSource(false); extendWithNode(arrayAccessNode); AssignmentNode arrayAccessAssignNode = translateAssignment(variable, new LocalVariableNode(variable), arrayAccessNode); extendWithNodeWithException(arrayAccessNode, nullPointerExceptionType); // translateAssignment() scans variable and creates new nodes, so set the expression // there, too. Node arrayAccessAssignNodeExpr = arrayAccessAssignNode.getExpression(); if (arrayAccessAssignNodeExpr instanceof ArrayAccessNode) { ((ArrayAccessNode) arrayAccessAssignNodeExpr).setArrayExpression(expression); } else if (arrayAccessAssignNodeExpr instanceof MethodInvocationNode) { // If the array component type is a primitive, there may be a boxing or unboxing // conversion. Treat that as an iterator. MethodInvocationNode boxingNode = (MethodInvocationNode) arrayAccessAssignNodeExpr; boxingNode.setIterableExpression(expression); } assert statement != null; scan(statement, p); // Loop back edge addLabelForNextNode(updateStart); IdentifierTree indexUse3 = treeBuilder.buildVariableUse(indexVariable); handleArtificialTree(indexUse3); LocalVariableNode indexNode3 = new LocalVariableNode(indexUse3); indexNode3.setInSource(false); extendWithNode(indexNode3); LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); handleArtificialTree(oneTree); Node one = new IntegerLiteralNode(oneTree); one.setInSource(false); extendWithNode(one); BinaryTree addOneTree = treeBuilder.buildBinary(intType, Tree.Kind.PLUS, indexUse3, oneTree); handleArtificialTree(addOneTree); Node addOneNode = new NumericalAdditionNode(addOneTree, indexNode3, one); addOneNode.setInSource(false); extendWithNode(addOneNode); AssignmentTree assignTree = treeBuilder.buildAssignment(indexUse3, addOneTree); handleArtificialTree(assignTree); Node assignNode = new AssignmentNode(assignTree, indexNode3, addOneNode); assignNode.setInSource(false); extendWithNode(assignNode); extendWithExtendedNode(new UnconditionalJump(conditionStart)); } // Loop exit addLabelForNextNode(loopExit); breakTargetLC = oldBreakTargetLC; continueTargetLC = oldContinueTargetLC; return null; } protected VariableTree createEnhancedForLoopIteratorVariable( MethodInvocationTree iteratorCall, VariableElement variableElement) { TypeMirror iteratorType = TreeUtils.typeOf(iteratorCall); // Declare and initialize a new, unique iterator variable VariableTree iteratorVariable = treeBuilder.buildVariableDecl( iteratorType, // annotatedIteratorTypeTree, uniqueName("iter"), variableElement.getEnclosingElement(), iteratorCall); return iteratorVariable; } protected VariableTree createEnhancedForLoopArrayVariable( ExpressionTree expression, VariableElement variableElement) { TypeMirror arrayType = TreeUtils.typeOf(expression); // Declare and initialize a temporary array variable VariableTree arrayVariable = treeBuilder.buildVariableDecl( arrayType, uniqueName("array"), variableElement.getEnclosingElement(), expression); return arrayVariable; } @Override public Node visitForLoop(ForLoopTree tree, Void p) { Name parentLabel = getLabel(getCurrentPath()); Label conditionStart = new Label(); Label loopEntry = new Label(); Label loopExit = new Label(); // If the loop is a labeled statement, then its continue target is identical for continues // with no label and continues with the loop's label. Label updateStart; if (parentLabel != null) { updateStart = continueLabels.get(parentLabel); } else { updateStart = new Label(); } LabelCell oldBreakTargetLC = breakTargetLC; breakTargetLC = new LabelCell(loopExit); LabelCell oldContinueTargetLC = continueTargetLC; continueTargetLC = new LabelCell(updateStart); // Initializer for (StatementTree init : tree.getInitializer()) { scan(init, p); } // Condition addLabelForNextNode(conditionStart); if (tree.getCondition() != null) { unbox(scan(tree.getCondition(), p)); ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); extendWithExtendedNode(cjump); } // Loop body addLabelForNextNode(loopEntry); assert tree.getStatement() != null; scan(tree.getStatement(), p); // Update addLabelForNextNode(updateStart); for (ExpressionStatementTree update : tree.getUpdate()) { scan(update, p); } extendWithExtendedNode(new UnconditionalJump(conditionStart)); // Loop exit addLabelForNextNode(loopExit); breakTargetLC = oldBreakTargetLC; continueTargetLC = oldContinueTargetLC; return null; } @Override public Node visitIdentifier(IdentifierTree tree, Void p) { Node node; if (TreeUtils.isFieldAccess(tree)) { Node receiver = getReceiver(tree); node = new FieldAccessNode(tree, receiver); } else { Element element = TreeUtils.elementFromUse(tree); switch (element.getKind()) { case FIELD: // Note that "this"/"super" is a field, but not a field access. if (element.getSimpleName().contentEquals("this")) { node = new ExplicitThisNode(tree); } else { node = new SuperNode(tree); } break; case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case RESOURCE_VARIABLE: case PARAMETER: node = new LocalVariableNode(tree); break; case PACKAGE: node = new PackageNameNode(tree); break; default: if (ElementUtils.isTypeDeclaration(element)) { node = new ClassNameNode(tree); break; } else if (ElementUtils.isBindingVariable(element)) { // Note: BINDING_VARIABLE should be added as a direct case above when // instanceof pattern matching and Java15 are supported. node = new LocalVariableNode(tree); break; } throw new BugInCF("bad element kind " + element.getKind()); } } if (node instanceof ClassNameNode) { extendWithClassNameNode((ClassNameNode) node); } else { extendWithNode(node); } return node; } @Override public Node visitIf(IfTree tree, Void p) { // all necessary labels Label thenEntry = new Label(); Label elseEntry = new Label(); Label endIf = new Label(); // basic block for the condition unbox(scan(tree.getCondition(), p)); ConditionalJump cjump = new ConditionalJump(thenEntry, elseEntry); extendWithExtendedNode(cjump); // then branch addLabelForNextNode(thenEntry); StatementTree thenStatement = tree.getThenStatement(); scan(thenStatement, p); extendWithExtendedNode(new UnconditionalJump(endIf)); // else branch addLabelForNextNode(elseEntry); StatementTree elseStatement = tree.getElseStatement(); if (elseStatement != null) { scan(elseStatement, p); } // label the end of the if statement addLabelForNextNode(endIf); return null; } @Override public Node visitImport(ImportTree tree, Void p) { throw new BugInCF("ImportTree is unexpected in AST to CFG translation: " + tree); } @Override public Node visitArrayAccess(ArrayAccessTree tree, Void p) { Node array = scan(tree.getExpression(), p); Node index = unaryNumericPromotion(scan(tree.getIndex(), p)); Node arrayAccess = new ArrayAccessNode(tree, array, index); extendWithNode(arrayAccess); extendWithNodeWithException(arrayAccess, arrayIndexOutOfBoundsExceptionType); extendWithNodeWithException(arrayAccess, nullPointerExceptionType); return arrayAccess; } @Override public Node visitLabeledStatement(LabeledStatementTree tree, Void p) { // This method can set the break target after generating all Nodes in the contained // statement, but it can't set the continue target, which may be in the middle of a // sequence of nodes. Labeled loops must look up and use the continue Labels. Name labelName = tree.getLabel(); Label breakLabel = new Label(labelName + "_break"); Label continueLabel = new Label(labelName + "_continue"); breakLabels.put(labelName, breakLabel); continueLabels.put(labelName, continueLabel); scan(tree.getStatement(), p); addLabelForNextNode(breakLabel); breakLabels.remove(labelName); continueLabels.remove(labelName); return null; } @Override public Node visitLiteral(LiteralTree tree, Void p) { Node r = null; switch (tree.getKind()) { case BOOLEAN_LITERAL: r = new BooleanLiteralNode(tree); break; case CHAR_LITERAL: r = new CharacterLiteralNode(tree); break; case DOUBLE_LITERAL: r = new DoubleLiteralNode(tree); break; case FLOAT_LITERAL: r = new FloatLiteralNode(tree); break; case INT_LITERAL: r = new IntegerLiteralNode(tree); break; case LONG_LITERAL: r = new LongLiteralNode(tree); break; case NULL_LITERAL: r = new NullLiteralNode(tree); break; case STRING_LITERAL: r = new StringLiteralNode(tree); break; default: throw new BugInCF("unexpected literal tree: " + tree); } assert r != null : "unexpected literal tree"; extendWithNode(r); return r; } @Override public Node visitMethod(MethodTree tree, Void p) { throw new BugInCF("MethodTree is unexpected in AST to CFG translation"); } @Override public Node visitModifiers(ModifiersTree tree, Void p) { throw new BugInCF("ModifiersTree is unexpected in AST to CFG translation"); } @Override public Node visitNewArray(NewArrayTree tree, Void p) { // see JLS 15.10 ArrayType type = (ArrayType) TreeUtils.typeOf(tree); TypeMirror elemType = type.getComponentType(); List dimensions = tree.getDimensions(); List initializers = tree.getInitializers(); assert dimensions != null; List dimensionNodes = CollectionsPlume.mapList(dim -> unaryNumericPromotion(scan(dim, p)), dimensions); List initializerNodes; if (initializers == null) { initializerNodes = Collections.emptyList(); } else { initializerNodes = CollectionsPlume.mapList( init -> assignConvert(scan(init, p), elemType), initializers); } Node node = new ArrayCreationNode(tree, type, dimensionNodes, initializerNodes); extendWithNodeWithExceptions(node, newArrayExceptionTypes); return node; } @Override public Node visitNewClass(NewClassTree tree, Void p) { // see JLS 15.9 DeclaredType classType = (DeclaredType) TreeUtils.typeOf(tree); TypeMirror enclosingType = classType.getEnclosingType(); Tree enclosingExpr = tree.getEnclosingExpression(); Node enclosingExprNode; if (enclosingExpr != null) { enclosingExprNode = scan(enclosingExpr, p); } else if (enclosingType.getKind() == TypeKind.DECLARED) { // This is an inner class (instance nested class). // As there is no explicit enclosing expression, create a node for the implicit this // argument. enclosingExprNode = new ImplicitThisNode(enclosingType); extendWithNode(enclosingExprNode); } else { // For static nested classes, the kind would be Typekind.None. enclosingExprNode = null; } // Convert constructor arguments ExecutableElement constructor = TreeUtils.elementFromUse(tree); List actualExprs = tree.getArguments(); List arguments = convertCallArguments( tree, constructor, TreeUtils.typeFromUse(tree), actualExprs, tree); // TODO: for anonymous classes, don't use the identifier alone. // See https://github.com/typetools/checker-framework/issues/890 . Node constructorNode = scan(tree.getIdentifier(), p); // Handle anonymous classes in visitClass. // Note that getClassBody() and therefore classbody can be null. ClassDeclarationNode classbody = (ClassDeclarationNode) scan(tree.getClassBody(), p); Node node = new ObjectCreationNode( tree, enclosingExprNode, constructorNode, arguments, classbody); List thrownTypes = constructor.getThrownTypes(); Set thrownSet = ArraySet.newArraySetOrLinkedHashSet( thrownTypes.size() + uncheckedExceptionTypes.size()); // Add exceptions explicitly mentioned in the throws clause. thrownSet.addAll(thrownTypes); // Add types to account for unchecked exceptions thrownSet.addAll(uncheckedExceptionTypes); extendWithNodeWithExceptions(node, thrownSet); return node; } /** * Maps a {@code Tree} to its directly enclosing {@code ParenthesizedTree} if one exists. * *

This map is used by {@link CFGTranslationPhaseOne#addToLookupMap(Node)} to associate a * {@code ParenthesizedTree} with the dataflow {@code Node} that was used during inference. This * map is necessary because dataflow does not create a {@code Node} for a {@code * ParenthesizedTree}. */ private final Map parenMapping = new HashMap<>(); @Override public Node visitParenthesized(ParenthesizedTree tree, Void p) { parenMapping.put(tree.getExpression(), tree); return scan(tree.getExpression(), p); } @Override public Node visitReturn(ReturnTree tree, Void p) { ExpressionTree ret = tree.getExpression(); // TODO: also have a return-node if nothing is returned ReturnNode result = null; if (ret != null) { Node node = scan(ret, p); result = new ReturnNode(tree, node, env.getTypeUtils()); returnNodes.add(result); extendWithNode(result); } extendWithExtendedNode(new UnconditionalJump(this.returnTargetLC.accessLabel())); return result; } @Override public Node visitMemberSelect(MemberSelectTree tree, Void p) { Node expr = scan(tree.getExpression(), p); if (!TreeUtils.isFieldAccess(tree)) { // Could be a selector of a class or package Element element = TreeUtils.elementFromUse(tree); if (ElementUtils.isTypeElement(element)) { ClassNameNode result = new ClassNameNode(tree, expr); extendWithClassNameNode(result); return result; } else if (element.getKind() == ElementKind.PACKAGE) { Node result = new PackageNameNode(tree, (PackageNameNode) expr); extendWithNode(result); return result; } else { throw new BugInCF("Unexpected element kind: " + element.getKind()); } } Node node = new FieldAccessNode(tree, expr); Element element = TreeUtils.elementFromUse(tree); if (ElementUtils.isStatic(element) || expr instanceof ThisNode) { // No NullPointerException can be thrown, use normal node extendWithNode(node); } else { extendWithNodeWithException(node, nullPointerExceptionType); } return node; } @Override public Node visitEmptyStatement(EmptyStatementTree tree, Void p) { return null; } @Override public Node visitSynchronized(SynchronizedTree tree, Void p) { // see JLS 14.19 Node synchronizedExpr = scan(tree.getExpression(), p); SynchronizedNode synchronizedStartNode = new SynchronizedNode(tree, synchronizedExpr, true, env.getTypeUtils()); extendWithNode(synchronizedStartNode); scan(tree.getBlock(), p); SynchronizedNode synchronizedEndNode = new SynchronizedNode(tree, synchronizedExpr, false, env.getTypeUtils()); extendWithNode(synchronizedEndNode); return null; } @Override public Node visitThrow(ThrowTree tree, Void p) { Node expression = scan(tree.getExpression(), p); TypeMirror exception = expression.getType(); ThrowNode throwsNode = new ThrowNode(tree, expression, env.getTypeUtils()); NodeWithExceptionsHolder exNode = extendWithNodeWithException(throwsNode, exception); exNode.setTerminatesExecution(true); return throwsNode; } @Override public Node visitCompilationUnit(CompilationUnitTree tree, Void p) { throw new BugInCF("CompilationUnitTree is unexpected in AST to CFG translation"); } /** * Return the first argument if it is non-null, otherwise return the second argument. Throws an * exception if both arguments are null. * * @param the type of the arguments * @param first a reference * @param second a reference * @return the first argument that is non-null */ private static A firstNonNull(A first, A second) { if (first != null) { return first; } else if (second != null) { return second; } else { throw new NullPointerException(); } } @Override public Node visitTry(TryTree tree, Void p) { List catches = tree.getCatches(); BlockTree finallyBlock = tree.getFinallyBlock(); extendWithNode( new MarkerNode( tree, "start of try statement #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); List> catchLabels = CollectionsPlume.mapList( (CatchTree c) -> IPair.of(TreeUtils.typeOf(c.getParameter().getType()), new Label()), catches); // Store return/break/continue labels, just in case we need them for a finally block. LabelCell oldReturnTargetLC = returnTargetLC; LabelCell oldBreakTargetLC = breakTargetLC; Map oldBreakLabels = breakLabels; LabelCell oldContinueTargetLC = continueTargetLC; Map oldContinueLabels = continueLabels; Label finallyLabel = null; Label exceptionalFinallyLabel = null; if (finallyBlock != null) { finallyLabel = new Label(); exceptionalFinallyLabel = new Label(); tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); returnTargetLC = new LabelCell(); breakTargetLC = new LabelCell(); breakLabels = new TryFinallyScopeMap(); continueTargetLC = new LabelCell(); continueLabels = new TryFinallyScopeMap(); } Label doneLabel = new Label(); tryStack.pushFrame(new TryCatchFrame(types, catchLabels)); extendWithNode( new MarkerNode( tree, "start of try block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); handleTryResourcesAndBlock(tree, p, tree.getResources()); extendWithNode( new MarkerNode( tree, "end of try block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); // This pops the try-catch frame tryStack.popFrame(); int catchIndex = 0; for (CatchTree c : catches) { addLabelForNextNode(catchLabels.get(catchIndex).second); TypeMirror catchType = TreeUtils.typeOf(c.getParameter().getType()); extendWithNode(new CatchMarkerNode(tree, "start", catchType, env.getTypeUtils())); scan(c, p); extendWithNode(new CatchMarkerNode(tree, "end", catchType, env.getTypeUtils())); catchIndex++; extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); } if (finallyLabel != null) { handleFinally( tree, doneLabel, finallyLabel, exceptionalFinallyLabel, () -> scan(finallyBlock, p), oldReturnTargetLC, oldBreakTargetLC, oldBreakLabels, oldContinueTargetLC, oldContinueLabels); } addLabelForNextNode(doneLabel); return null; } /** * A recursive helper method to handle the resource declarations (if any) in a {@link TryTree} * and its main block. If the {@code resources} list is empty, the method scans the main block * of the try statement and returns. Otherwise, the first resource declaration in {@code * resources} is desugared, following the logic in JLS 14.20.3.1. A resource declaration * r is desugared by adding the nodes for r itself to the CFG, followed by a * synthetic nested {@code try} block and {@code finally} block. The synthetic {@code try} block * contains any remaining resource declarations and the original try block (handled via * recursion). The synthetic {@code finally} block contains a call to {@code close} for * r, guaranteeing that on every path through the CFG, r is closed. * * @param tryTree the original try tree (with 0 or more resources) from the AST * @param p the value to pass to calls to {@code scan} * @param resources the remaining resource declarations to handle */ private void handleTryResourcesAndBlock( TryTree tryTree, Void p, List resources) { if (resources.isEmpty()) { // Either `tryTree` was not a try-with-resources, or this method was called recursively // and all the resources have been handled. Just scan the main try block. scan(tryTree.getBlock(), p); return; } // Handle the first resource declaration in the list. The rest will be handled by a // recursive call. Tree resourceDeclarationTree = resources.get(0); extendWithNode( new MarkerNode( resourceDeclarationTree, "start of try for resource #" + TreeUtils.treeUids.get(resourceDeclarationTree), env.getTypeUtils())); // Store return/break/continue labels. Generating a synthetic finally block for closing the // resource requires creating fresh return/break/continue labels and then restoring the old // labels afterward. LabelCell oldReturnTargetLC = returnTargetLC; LabelCell oldBreakTargetLC = breakTargetLC; Map oldBreakLabels = breakLabels; LabelCell oldContinueTargetLC = continueTargetLC; Map oldContinueLabels = continueLabels; // Add nodes for the resource declaration to the CFG. NOTE: it is critical to add these // nodes *before* pushing a TryFinallyFrame for the finally block that will close the // resource. // If any exception occurs due to code within the resource declaration, the corresponding // variable or field is *not* automatically closed (as it was never assigned a value). Node resourceCloseNode = scan(resourceDeclarationTree, p); // Now, set things up for our synthetic finally block that closes the resource. Label doneLabel = new Label(); Label finallyLabel = new Label(); Label exceptionalFinallyLabel = new Label(); tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); returnTargetLC = new LabelCell(); breakTargetLC = new LabelCell(); breakLabels = new TryFinallyScopeMap(); continueTargetLC = new LabelCell(); continueLabels = new TryFinallyScopeMap(); extendWithNode( new MarkerNode( resourceDeclarationTree, "start of try block for resource #" + TreeUtils.treeUids.get(resourceDeclarationTree), env.getTypeUtils())); // Recursively handle any remaining resource declarations and the main block of the try handleTryResourcesAndBlock(tryTree, p, resources.subList(1, resources.size())); extendWithNode( new MarkerNode( resourceDeclarationTree, "end of try block for resource #" + TreeUtils.treeUids.get(resourceDeclarationTree), env.getTypeUtils())); extendWithExtendedNode(new UnconditionalJump(finallyLabel)); // Generate the finally block that closes the resource handleFinally( resourceDeclarationTree, doneLabel, finallyLabel, exceptionalFinallyLabel, () -> addCloseCallForResource(resourceDeclarationTree, resourceCloseNode), oldReturnTargetLC, oldBreakTargetLC, oldBreakLabels, oldContinueTargetLC, oldContinueLabels); addLabelForNextNode(doneLabel); } /** * Adds a synthetic {@code close} call to the CFG to close some resource variable declared or * used in a try-with-resources. * * @param resourceDeclarationTree the resource declaration * @param resourceToCloseNode node represented the variable or field on which {@code close} * should be invoked */ private void addCloseCallForResource(Tree resourceDeclarationTree, Node resourceToCloseNode) { Tree receiverTree = resourceDeclarationTree; if (receiverTree instanceof VariableTree) { receiverTree = treeBuilder.buildVariableUse((VariableTree) receiverTree); handleArtificialTree(receiverTree); } MemberSelectTree closeSelect = treeBuilder.buildCloseMethodAccess((ExpressionTree) receiverTree); handleArtificialTree(closeSelect); MethodInvocationTree closeCall = treeBuilder.buildMethodInvocation(closeSelect); handleArtificialTree(closeCall); Node receiverNode = resourceToCloseNode; if (receiverNode instanceof AssignmentNode) { // variable declaration; use the LHS receiverNode = ((AssignmentNode) resourceToCloseNode).getTarget(); } // TODO do we need to insert some kind of node representing a use of receiverNode // (which can be either a LocalVariableNode or a FieldAccessNode)? MethodAccessNode closeAccessNode = new MethodAccessNode(closeSelect, receiverNode); closeAccessNode.setInSource(false); extendWithNode(closeAccessNode); MethodInvocationNode closeCallNode = new MethodInvocationNode( closeCall, closeAccessNode, Collections.emptyList(), getCurrentPath()); closeCallNode.setInSource(false); extendWithMethodInvocationNode(TreeUtils.elementFromUse(closeCall), closeCallNode); } /** * Shared logic for CFG generation for a finally block. The block may correspond to a {@link * TryTree} originally in the source code, or it may be a synthetic finally block used to model * closing of a resource due to try-with-resources. * * @param markerTree tree to reference when creating {@link MarkerNode}s for the finally block * @param doneLabel label for the normal successor of the try block (no exceptions, returns, * breaks, or continues) * @param finallyLabel label for the entry of the finally block for the normal case * @param exceptionalFinallyLabel label for entry of the finally block for when the try block * throws an exception * @param finallyBlockCFGGenerator generates CFG nodes and edges for the finally block * @param oldReturnTargetLC old return target label cell, which gets restored to {@link * #returnTargetLC} while handling the finally block * @param oldBreakTargetLC old break target label cell, which gets restored to {@link * #breakTargetLC} while handling the finally block * @param oldBreakLabels old break labels, which get restored to {@link #breakLabels} while * handling the finally block * @param oldContinueTargetLC old continue target label cell, which gets restored to {@link * #continueTargetLC} while handling the finally block * @param oldContinueLabels old continue labels, which get restored to {@link #continueLabels} * while handling the finally block */ private void handleFinally( Tree markerTree, Label doneLabel, Label finallyLabel, Label exceptionalFinallyLabel, Runnable finallyBlockCFGGenerator, LabelCell oldReturnTargetLC, LabelCell oldBreakTargetLC, Map oldBreakLabels, LabelCell oldContinueTargetLC, Map oldContinueLabels) { // Reset values before analyzing the finally block! tryStack.popFrame(); { // Scan 'finallyBlock' for only 'finallyLabel' (a successful path) addLabelForNextNode(finallyLabel); extendWithNode( new MarkerNode( markerTree, "start of finally block #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); finallyBlockCFGGenerator.run(); extendWithNode( new MarkerNode( markerTree, "end of finally block #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); extendWithExtendedNode(new UnconditionalJump(doneLabel)); } if (hasExceptionalPath(exceptionalFinallyLabel)) { // If an exceptional path exists, scan 'finallyBlock' for 'exceptionalFinallyLabel', // and scan copied 'finallyBlock' for 'finallyLabel' (a successful path). If there // is no successful path, it will be removed in later phase. // TODO: Don't we need a separate finally block for each kind of exception? addLabelForNextNode(exceptionalFinallyLabel); extendWithNode( new MarkerNode( markerTree, "start of finally block for Throwable #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); finallyBlockCFGGenerator.run(); NodeWithExceptionsHolder throwing = extendWithNodeWithException( new MarkerNode( markerTree, "end of finally block for Throwable #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils()), throwableType); throwing.setTerminatesExecution(true); } if (returnTargetLC.wasAccessed()) { addLabelForNextNode(returnTargetLC.peekLabel()); returnTargetLC = oldReturnTargetLC; extendWithNode( new MarkerNode( markerTree, "start of finally block for return #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); finallyBlockCFGGenerator.run(); extendWithNode( new MarkerNode( markerTree, "end of finally block for return #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); extendWithExtendedNode(new UnconditionalJump(returnTargetLC.accessLabel())); } else { returnTargetLC = oldReturnTargetLC; } if (breakTargetLC.wasAccessed()) { addLabelForNextNode(breakTargetLC.peekLabel()); breakTargetLC = oldBreakTargetLC; extendWithNode( new MarkerNode( markerTree, "start of finally block for break #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); finallyBlockCFGGenerator.run(); extendWithNode( new MarkerNode( markerTree, "end of finally block for break #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel())); } else { breakTargetLC = oldBreakTargetLC; } Map accessedBreakLabels = ((TryFinallyScopeMap) breakLabels).getAccessedNames(); if (!accessedBreakLabels.isEmpty()) { breakLabels = oldBreakLabels; for (Map.Entry access : accessedBreakLabels.entrySet()) { addLabelForNextNode(access.getValue()); extendWithNode( new MarkerNode( markerTree, "start of finally block for break label " + access.getKey() + " #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); finallyBlockCFGGenerator.run(); extendWithNode( new MarkerNode( markerTree, "end of finally block for break label " + access.getKey() + " #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); extendWithExtendedNode(new UnconditionalJump(breakLabels.get(access.getKey()))); } } else { breakLabels = oldBreakLabels; } if (continueTargetLC.wasAccessed()) { addLabelForNextNode(continueTargetLC.peekLabel()); continueTargetLC = oldContinueTargetLC; extendWithNode( new MarkerNode( markerTree, "start of finally block for continue #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); finallyBlockCFGGenerator.run(); extendWithNode( new MarkerNode( markerTree, "end of finally block for continue #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); extendWithExtendedNode(new UnconditionalJump(continueTargetLC.accessLabel())); } else { continueTargetLC = oldContinueTargetLC; } Map accessedContinueLabels = ((TryFinallyScopeMap) continueLabels).getAccessedNames(); if (!accessedContinueLabels.isEmpty()) { continueLabels = oldContinueLabels; for (Map.Entry access : accessedContinueLabels.entrySet()) { addLabelForNextNode(access.getValue()); extendWithNode( new MarkerNode( markerTree, "start of finally block for continue label " + access.getKey() + " #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); finallyBlockCFGGenerator.run(); extendWithNode( new MarkerNode( markerTree, "end of finally block for continue label " + access.getKey() + " #" + TreeUtils.treeUids.get(markerTree), env.getTypeUtils())); extendWithExtendedNode(new UnconditionalJump(continueLabels.get(access.getKey()))); } } else { continueLabels = oldContinueLabels; } } /** * Returns whether an exceptional node for {@code target} exists in {@link #nodeList} or not. * * @param target label for exception * @return true when an exceptional node for {@code target} exists in {@link #nodeList} */ private boolean hasExceptionalPath(Label target) { for (ExtendedNode node : nodeList) { if (node instanceof NodeWithExceptionsHolder) { NodeWithExceptionsHolder exceptionalNode = (NodeWithExceptionsHolder) node; for (Set

This can be used to handle system types that are not present. For example, in Java code * that is translated to JavaScript using j2cl, the custom bootclasspath contains APIs that are * emulated in JavaScript, so some types such as OutOfMemoryError are deliberately not present. * * @param clazz a class, which must have a canonical name * @return the TypeMirror for the class, or {@code null} if the type is not present */ protected @Nullable TypeMirror maybeGetTypeMirror(Class clazz) { String name = clazz.getCanonicalName(); assert name != null : clazz + " does not have a canonical name"; TypeElement element = elements.getTypeElement(name); if (element == null) { return null; } return element.asType(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy