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

org.checkerframework.dataflow.cfg.CFGBuilder Maven / Gradle / Ivy

package org.checkerframework.dataflow.cfg;

/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
*/

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.Tree.Kind;
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.TreePathScanner;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
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.PrimitiveType;
import javax.lang.model.type.ReferenceType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.UnionType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.checkerframework.dataflow.analysis.Store;
import org.checkerframework.dataflow.cfg.CFGBuilder.ExtendedNode.ExtendedNodeType;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
import org.checkerframework.dataflow.cfg.block.Block;
import org.checkerframework.dataflow.cfg.block.Block.BlockType;
import org.checkerframework.dataflow.cfg.block.BlockImpl;
import org.checkerframework.dataflow.cfg.block.ConditionalBlockImpl;
import org.checkerframework.dataflow.cfg.block.ExceptionBlock;
import org.checkerframework.dataflow.cfg.block.ExceptionBlockImpl;
import org.checkerframework.dataflow.cfg.block.RegularBlock;
import org.checkerframework.dataflow.cfg.block.RegularBlockImpl;
import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl;
import org.checkerframework.dataflow.cfg.block.SpecialBlock.SpecialBlockType;
import org.checkerframework.dataflow.cfg.block.SpecialBlockImpl;
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.CharacterLiteralNode;
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.DoubleLiteralNode;
import org.checkerframework.dataflow.cfg.node.EqualToNode;
import org.checkerframework.dataflow.cfg.node.ExplicitThisLiteralNode;
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.ImplicitThisLiteralNode;
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.StringConcatenateAssignmentNode;
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.SynchronizedNode;
import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
import org.checkerframework.dataflow.cfg.node.ThisLiteralNode;
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.TerminatesExecution;
import org.checkerframework.dataflow.util.MostlySingleton;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.BasicAnnotationProvider;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.trees.TreeBuilder;

/**
 * Builds the control flow graph of some Java code (either a method, or an arbitrary statement).
 *
 * 

The translation of the AST to the CFG is split into three phases: * *

    *
  1. Phase one. In the first phase, the AST is translated into a sequence of {@link * org.checkerframework.dataflow.cfg.CFGBuilder.ExtendedNode}s. An extended node can either be * a {@link Node}, or one of several meta elements such as a conditional or unconditional jump * or a node with additional information about exceptions. Some of the extended nodes contain * labels (e.g., for the jump target), and phase one additionally creates a mapping from * labels to extended nodes. Finally, the list of leaders is computed: A leader is an extended * node which will give rise to a basic block in phase two. *
  2. Phase two. In this phase, the sequence of extended nodes is translated to a graph * of control flow blocks that contain nodes. The meta elements from phase one are translated * into the correct edges. *
  3. Phase three. The control flow graph generated in phase two can contain degenerate * basic blocks such as empty regular basic blocks or conditional basic blocks that have the * same block as both 'then' and 'else' successor. This phase removes these cases while * preserving the control flow structure. *
* * @author Stefan Heule */ public class CFGBuilder { /** Can assertions be assumed to be disabled? */ protected final boolean assumeAssertionsDisabled; /** Can assertions be assumed to be enabled? */ protected final boolean assumeAssertionsEnabled; public CFGBuilder(boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled) { assert !(assumeAssertionsDisabled && assumeAssertionsEnabled); this.assumeAssertionsEnabled = assumeAssertionsEnabled; this.assumeAssertionsDisabled = assumeAssertionsDisabled; } /** * Class declarations that have been encountered when building the control-flow graph for a * method. */ protected final List declaredClasses = new ArrayList<>(); public List getDeclaredClasses() { return declaredClasses; } /** * Lambdas encountered when building the control-flow graph for a method, variable initializer, * or initializer. */ protected final List declaredLambdas = new ArrayList<>(); public List getDeclaredLambdas() { return declaredLambdas; } /** Build the control flow graph of some code. */ public static ControlFlowGraph build( CompilationUnitTree root, ProcessingEnvironment env, UnderlyingAST underlyingAST, boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled) { return new CFGBuilder(assumeAssertionsEnabled, assumeAssertionsDisabled) .run(root, env, underlyingAST); } /** * Build the control flow graph of some code (method, initializer block, ...). bodyPath is the * TreePath to the body of that code. */ public static ControlFlowGraph build( TreePath bodyPath, ProcessingEnvironment env, UnderlyingAST underlyingAST, boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled) { return new CFGBuilder(assumeAssertionsEnabled, assumeAssertionsDisabled) .run(bodyPath, env, underlyingAST); } /** Build the control flow graph of a method. */ public static ControlFlowGraph build( CompilationUnitTree root, ProcessingEnvironment env, MethodTree tree, ClassTree classTree, boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled) { return new CFGBuilder(assumeAssertionsEnabled, assumeAssertionsDisabled) .run(root, env, tree, classTree); } /** Build the control flow graph of some code. */ public static ControlFlowGraph build( CompilationUnitTree root, ProcessingEnvironment env, UnderlyingAST underlyingAST) { return new CFGBuilder(false, false).run(root, env, underlyingAST); } /** Build the control flow graph of a method. */ public static ControlFlowGraph build( CompilationUnitTree root, ProcessingEnvironment env, MethodTree tree, ClassTree classTree) { return new CFGBuilder(false, false).run(root, env, tree, classTree); } /** Build the control flow graph of some code. */ public ControlFlowGraph run( CompilationUnitTree root, ProcessingEnvironment env, UnderlyingAST underlyingAST) { declaredClasses.clear(); declaredLambdas.clear(); TreeBuilder builder = new TreeBuilder(env); AnnotationProvider annotationProvider = new BasicAnnotationProvider(); PhaseOneResult phase1result = new CFGTranslationPhaseOne() .process( root, env, underlyingAST, exceptionalExitLabel, builder, annotationProvider); ControlFlowGraph phase2result = new CFGTranslationPhaseTwo().process(phase1result); ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); return phase3result; } /** * Build the control flow graph of some code (method, initializer block, ...). bodyPath is the * TreePath to the body of that code. */ public ControlFlowGraph run( TreePath bodyPath, ProcessingEnvironment env, UnderlyingAST underlyingAST) { declaredClasses.clear(); TreeBuilder builder = new TreeBuilder(env); AnnotationProvider annotationProvider = new BasicAnnotationProvider(); PhaseOneResult phase1result = new CFGTranslationPhaseOne() .process( bodyPath, env, underlyingAST, exceptionalExitLabel, builder, annotationProvider); ControlFlowGraph phase2result = new CFGTranslationPhaseTwo().process(phase1result); ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); return phase3result; } /** Build the control flow graph of a method. */ public ControlFlowGraph run( CompilationUnitTree root, ProcessingEnvironment env, MethodTree tree, ClassTree classTree) { UnderlyingAST underlyingAST = new CFGMethod(tree, classTree); return run(root, env, underlyingAST); } /* --------------------------------------------------------- */ /* Extended Node Types and Labels */ /* --------------------------------------------------------- */ /** Special label to identify the exceptional exit. */ protected final Label exceptionalExitLabel = new Label(); /** Special label to identify the regular exit. */ protected final Label regularExitLabel = new Label(); /** * An extended node can be one of several things (depending on its {@code type}): * *
    *
  • NODE. An extended node of this type is just a wrapper for a {@link Node} (that * cannot throw exceptions). *
  • EXCEPTION_NODE. A wrapper for a {@link Node} which can throw exceptions. It * contains a label for every possible exception type the node might throw. *
  • UNCONDITIONAL_JUMP. An unconditional jump to a label. *
  • TWO_TARGET_CONDITIONAL_JUMP. A conditional jump with two targets for both the * 'then' and 'else' branch. *
*/ protected abstract static class ExtendedNode { /** The basic block this extended node belongs to (as determined in phase two). */ protected BlockImpl block; /** Type of this node. */ protected ExtendedNodeType type; /** Does this node terminate the execution? (e.g., "System.exit()") */ protected boolean terminatesExecution = false; public ExtendedNode(ExtendedNodeType type) { this.type = type; } /** Extended node types (description see above). */ public enum ExtendedNodeType { NODE, EXCEPTION_NODE, UNCONDITIONAL_JUMP, CONDITIONAL_JUMP } public ExtendedNodeType getType() { return type; } public boolean getTerminatesExecution() { return terminatesExecution; } public void setTerminatesExecution(boolean terminatesExecution) { this.terminatesExecution = terminatesExecution; } /** * @return the node contained in this extended node (only applicable if the type is {@code * NODE} or {@code EXCEPTION_NODE}). */ public Node getNode() { assert false; return null; } /** * @return the label associated with this extended node (only applicable if type is {@link * ExtendedNodeType#CONDITIONAL_JUMP} or {@link ExtendedNodeType#UNCONDITIONAL_JUMP}). */ public Label getLabel() { assert false; return null; } public BlockImpl getBlock() { return block; } public void setBlock(BlockImpl b) { this.block = b; } @Override public String toString() { return "ExtendedNode(" + type + ")"; } } /** An extended node of type {@code NODE}. */ protected static class NodeHolder extends ExtendedNode { protected Node node; public NodeHolder(Node node) { super(ExtendedNodeType.NODE); this.node = node; } @Override public Node getNode() { return node; } @Override public String toString() { return "NodeHolder(" + node + ")"; } } /** An extended node of type {@code EXCEPTION_NODE}. */ protected static class NodeWithExceptionsHolder extends ExtendedNode { protected Node node; /** * Map from exception type to labels of successors that may be reached as a result of that * exception. */ protected Map> exceptions; public NodeWithExceptionsHolder(Node node, Map> exceptions) { super(ExtendedNodeType.EXCEPTION_NODE); this.node = node; this.exceptions = exceptions; } public Map> getExceptions() { return exceptions; } @Override public Node getNode() { return node; } @Override public String toString() { return "NodeWithExceptionsHolder(" + node + ")"; } } /** * An extended node of type {@link ExtendedNodeType#CONDITIONAL_JUMP}. * *

Important: In the list of extended nodes, there should not be any labels that * point to a conditional jump. Furthermore, the node directly ahead of any conditional jump has * to be a {@link NodeWithExceptionsHolder} or {@link NodeHolder}, and the node held by that * extended node is required to be of boolean type. */ protected static class ConditionalJump extends ExtendedNode { protected Label trueSucc; protected Label falseSucc; protected Store.FlowRule trueFlowRule; protected Store.FlowRule falseFlowRule; public ConditionalJump(Label trueSucc, Label falseSucc) { super(ExtendedNodeType.CONDITIONAL_JUMP); this.trueSucc = trueSucc; this.falseSucc = falseSucc; } public Label getThenLabel() { return trueSucc; } public Label getElseLabel() { return falseSucc; } public Store.FlowRule getTrueFlowRule() { return trueFlowRule; } public Store.FlowRule getFalseFlowRule() { return falseFlowRule; } public void setTrueFlowRule(Store.FlowRule rule) { trueFlowRule = rule; } public void setFalseFlowRule(Store.FlowRule rule) { falseFlowRule = rule; } @Override public String toString() { return "TwoTargetConditionalJump(" + getThenLabel() + "," + getElseLabel() + ")"; } } /** An extended node of type {@link ExtendedNodeType#UNCONDITIONAL_JUMP}. */ protected static class UnconditionalJump extends ExtendedNode { protected Label jumpTarget; public UnconditionalJump(Label jumpTarget) { super(ExtendedNodeType.UNCONDITIONAL_JUMP); this.jumpTarget = jumpTarget; } @Override public Label getLabel() { return jumpTarget; } @Override public String toString() { return "JumpMarker(" + getLabel() + ")"; } } /** * A label is used to refer to other extended nodes using a mapping from labels to extended * nodes. Labels get their names either from labeled statements in the source code or from * internally generated unique names. */ protected static class Label { private static int uid = 0; protected String name; public Label(String name) { this.name = name; } public Label() { this.name = uniqueName(); } @Override public String toString() { return name; } /** * Return a new unique label name that cannot be confused with a Java source code label. * * @return a new unique label name */ private static String uniqueName() { return "%L" + uid++; } } /** * A TryFrame takes a thrown exception type and maps it to a set of possible control-flow * successors. */ protected static interface TryFrame { /** * Given a type of thrown exception, add the set of possible control flow successor {@link * Label}s to the argument set. Return true if the exception is known to be caught by one of * those labels and false if it may propagate still further. */ public boolean possibleLabels(TypeMirror thrown, Set

    *
  1. Empty regular basic blocks: These blocks will be removed and their predecessors linked * directly to the successor. *
  2. Conditional basic blocks that have the same basic block as the 'then' and 'else' * successor: The conditional basic block will be removed in this case. *
  3. Two consecutive, non-empty, regular basic blocks where the second block has exactly one * predecessor (namely the other of the two blocks): In this case, the two blocks are * merged. *
  4. Some basic blocks might not be reachable from the entryBlock. These basic blocks are * removed, and the list of predecessors (in the doubly-linked structure of basic blocks) * are adapted correctly. *
* * Eliminating the second type of degenerate cases might introduce cases of the third problem. * These are also removed. */ public static class CFGTranslationPhaseThree { /** * A simple wrapper object that holds a basic block and allows to set one of its successors. */ protected interface PredecessorHolder { void setSuccessor(BlockImpl b); BlockImpl getBlock(); } /** * Perform phase three on the control flow graph {@code cfg}. * * @param cfg the control flow graph. Ownership is transfered to this method and the caller * is not allowed to read or modify {@code cfg} after the call to {@code process} any * more. * @return the resulting control flow graph */ public static ControlFlowGraph process(ControlFlowGraph cfg) { Set worklist = cfg.getAllBlocks(); Set dontVisit = new HashSet<>(); // note: this method has to be careful when relinking basic blocks // to not forget to adjust the predecessors, too // fix predecessor lists by removing any unreachable predecessors for (Block c : worklist) { BlockImpl cur = (BlockImpl) c; for (BlockImpl pred : new HashSet<>(cur.getPredecessors())) { if (!worklist.contains(pred)) { cur.removePredecessor(pred); } } } // remove empty blocks for (Block cur : worklist) { if (dontVisit.contains(cur)) { continue; } if (cur.getType() == BlockType.REGULAR_BLOCK) { RegularBlockImpl b = (RegularBlockImpl) cur; if (b.isEmpty()) { Set empty = new HashSet<>(); Set predecessors = new HashSet<>(); BlockImpl succ = computeNeighborhoodOfEmptyBlock(b, empty, predecessors); for (RegularBlockImpl e : empty) { succ.removePredecessor(e); dontVisit.add(e); } for (PredecessorHolder p : predecessors) { BlockImpl block = p.getBlock(); dontVisit.add(block); succ.removePredecessor(block); p.setSuccessor(succ); } } } } // remove useless conditional blocks worklist = cfg.getAllBlocks(); for (Block c : worklist) { BlockImpl cur = (BlockImpl) c; if (cur.getType() == BlockType.CONDITIONAL_BLOCK) { ConditionalBlockImpl cb = (ConditionalBlockImpl) cur; assert cb.getPredecessors().size() == 1; if (cb.getThenSuccessor() == cb.getElseSuccessor()) { BlockImpl pred = cb.getPredecessors().iterator().next(); PredecessorHolder predecessorHolder = getPredecessorHolder(pred, cb); BlockImpl succ = (BlockImpl) cb.getThenSuccessor(); succ.removePredecessor(cb); predecessorHolder.setSuccessor(succ); } } } // merge consecutive basic blocks if possible worklist = cfg.getAllBlocks(); for (Block cur : worklist) { if (cur.getType() == BlockType.REGULAR_BLOCK) { RegularBlockImpl b = (RegularBlockImpl) cur; Block succ = b.getRegularSuccessor(); if (succ.getType() == BlockType.REGULAR_BLOCK) { RegularBlockImpl rs = (RegularBlockImpl) succ; if (rs.getPredecessors().size() == 1) { b.setSuccessor(rs.getRegularSuccessor()); b.addNodes(rs.getContents()); rs.getRegularSuccessor().removePredecessor(rs); } } } } // collect all reachable trees final Set allReachableTrees = Collections.newSetFromMap(new IdentityHashMap()); for (Block b : cfg.getAllBlocks()) { if (b instanceof RegularBlock) { for (Node n : ((RegularBlock) b).getContents()) { Tree tree = n.getTree(); if (tree != null) { allReachableTrees.add(tree); } } } else if (b instanceof ExceptionBlock) { Tree tree = ((ExceptionBlock) b).getNode().getTree(); if (tree != null) { allReachableTrees.add(tree); } } } // remove a generated tree if any child tree of it is unreachable because such tree is // not analyzed by dataflow framework and cause of false errors. ContainsAnyScanner s = new ContainsAnyScanner(allReachableTrees); for (List generatedTrees : cfg.generatedTreesLookupMap.values()) { Iterator generatedTreesIterator = generatedTrees.iterator(); while (generatedTreesIterator.hasNext()) { Tree root = generatedTreesIterator.next(); s.reset(); s.scan(root, null); if (!s.contains) { generatedTreesIterator.remove(); } } } return cfg; } /** * {@code contains} will be true after calling {@link #scan(Tree, Void)} if {@code trees} * contains {@code node} or any child tree of {@code node} */ private static class ContainsAnyScanner extends TreeScanner { private boolean contains = false; private final Set trees; private ContainsAnyScanner(Set trees) { this.trees = trees; } @Override public Void scan(Tree node, Void aVoid) { if (node == null) { return null; } if (trees.contains(node)) { contains = true; } if (!contains) { super.scan(node, aVoid); } return null; } private void reset() { contains = false; } } /** * Compute the set of empty regular basic blocks {@code empty}, starting at {@code start} * and going both forward and backwards. Furthermore, compute the predecessors of these * empty blocks ({@code predecessors} ), and their single successor (return value). * * @param start the starting point of the search (an empty, regular basic block) * @param empty an empty set to be filled by this method with all empty basic blocks found * (including {@code start}). * @param predecessors an empty set to be filled by this method with all predecessors * @return the single successor of the set of the empty basic blocks */ protected static BlockImpl computeNeighborhoodOfEmptyBlock( RegularBlockImpl start, Set empty, Set predecessors) { // get empty neighborhood that come before 'start' computeNeighborhoodOfEmptyBlockBackwards(start, empty, predecessors); // go forward BlockImpl succ = (BlockImpl) start.getSuccessor(); while (succ.getType() == BlockType.REGULAR_BLOCK) { RegularBlockImpl cur = (RegularBlockImpl) succ; if (cur.isEmpty()) { computeNeighborhoodOfEmptyBlockBackwards(cur, empty, predecessors); assert empty.contains(cur) : "cur ought to be in empty"; succ = (BlockImpl) cur.getSuccessor(); if (succ == cur) { // An infinite loop, making exit block unreachable break; } } else { break; } } return succ; } /** * Compute the set of empty regular basic blocks {@code empty}, starting at {@code start} * and looking only backwards in the control flow graph. Furthermore, compute the * predecessors of these empty blocks ( {@code predecessors}). * * @param start the starting point of the search (an empty, regular basic block) * @param empty a set to be filled by this method with all empty basic blocks found * (including {@code start}). * @param predecessors a set to be filled by this method with all predecessors */ protected static void computeNeighborhoodOfEmptyBlockBackwards( RegularBlockImpl start, Set empty, Set predecessors) { RegularBlockImpl cur = start; empty.add(cur); for (final BlockImpl pred : cur.getPredecessors()) { switch (pred.getType()) { case SPECIAL_BLOCK: // add pred correctly to predecessor list predecessors.add(getPredecessorHolder(pred, cur)); break; case CONDITIONAL_BLOCK: // add pred correctly to predecessor list predecessors.add(getPredecessorHolder(pred, cur)); break; case EXCEPTION_BLOCK: // add pred correctly to predecessor list predecessors.add(getPredecessorHolder(pred, cur)); break; case REGULAR_BLOCK: RegularBlockImpl r = (RegularBlockImpl) pred; if (r.isEmpty()) { // recursively look backwards if (!empty.contains(r)) { computeNeighborhoodOfEmptyBlockBackwards(r, empty, predecessors); } } else { // add pred correctly to predecessor list predecessors.add(getPredecessorHolder(pred, cur)); } break; } } } /** * Return a predecessor holder that can be used to set the successor of {@code pred} in the * place where previously the edge pointed to {@code cur}. Additionally, the predecessor * holder also takes care of unlinking (i.e., removing the {@code pred} from {@code cur's} * predecessors). */ protected static PredecessorHolder getPredecessorHolder( final BlockImpl pred, final BlockImpl cur) { switch (pred.getType()) { case SPECIAL_BLOCK: SingleSuccessorBlockImpl s = (SingleSuccessorBlockImpl) pred; return singleSuccessorHolder(s, cur); case CONDITIONAL_BLOCK: // add pred correctly to predecessor list final ConditionalBlockImpl c = (ConditionalBlockImpl) pred; if (c.getThenSuccessor() == cur) { return new PredecessorHolder() { @Override public void setSuccessor(BlockImpl b) { c.setThenSuccessor(b); cur.removePredecessor(pred); } @Override public BlockImpl getBlock() { return c; } }; } else { assert c.getElseSuccessor() == cur; return new PredecessorHolder() { @Override public void setSuccessor(BlockImpl b) { c.setElseSuccessor(b); cur.removePredecessor(pred); } @Override public BlockImpl getBlock() { return c; } }; } case EXCEPTION_BLOCK: // add pred correctly to predecessor list final ExceptionBlockImpl e = (ExceptionBlockImpl) pred; if (e.getSuccessor() == cur) { return singleSuccessorHolder(e, cur); } else { Set>> entrySet = e.getExceptionalSuccessors().entrySet(); for (final Entry> entry : entrySet) { if (entry.getValue().contains(cur)) { return new PredecessorHolder() { @Override public void setSuccessor(BlockImpl b) { e.addExceptionalSuccessor(b, entry.getKey()); cur.removePredecessor(pred); } @Override public BlockImpl getBlock() { return e; } }; } } } assert false; break; case REGULAR_BLOCK: RegularBlockImpl r = (RegularBlockImpl) pred; return singleSuccessorHolder(r, cur); } return null; } /** * @return a {@link PredecessorHolder} that sets the successor of a single successor block * {@code s}. */ protected static PredecessorHolder singleSuccessorHolder( final SingleSuccessorBlockImpl s, final BlockImpl old) { return new PredecessorHolder() { @Override public void setSuccessor(BlockImpl b) { s.setSuccessor(b); old.removePredecessor(s); } @Override public BlockImpl getBlock() { return s; } }; } } /* --------------------------------------------------------- */ /* Phase Two */ /* --------------------------------------------------------- */ /** Tuple class with up to three members. */ protected static class Tuple { public A a; public B b; public C c; public Tuple(A a, B b) { this.a = a; this.b = b; } public Tuple(A a, B b, C c) { this.a = a; this.b = b; this.c = c; } } /** Class that performs phase two of the translation process. */ public class CFGTranslationPhaseTwo { public CFGTranslationPhaseTwo() {} /** * Perform phase two of the translation. * * @param in the result of phase one * @return a control flow graph that might still contain degenerate basic block (such as * empty regular basic blocks or conditional blocks with the same block as 'then' and * 'else' sucessor) */ public ControlFlowGraph process(PhaseOneResult in) { Map bindings = in.bindings; ArrayList nodeList = in.nodeList; Set leaders = in.leaders; assert in.nodeList.size() > 0; // exit blocks SpecialBlockImpl regularExitBlock = new SpecialBlockImpl(SpecialBlockType.EXIT); SpecialBlockImpl exceptionalExitBlock = new SpecialBlockImpl(SpecialBlockType.EXCEPTIONAL_EXIT); // record missing edges that will be added later Set> missingEdges = new MostlySingleton<>(); // missing exceptional edges Set> missingExceptionalEdges = new HashSet<>(); // create start block SpecialBlockImpl startBlock = new SpecialBlockImpl(SpecialBlockType.ENTRY); missingEdges.add(new Tuple<>(startBlock, 0)); // loop through all 'leaders' (while dynamically detecting the // leaders) RegularBlockImpl block = new RegularBlockImpl(); int i = 0; for (ExtendedNode node : nodeList) { switch (node.getType()) { case NODE: if (leaders.contains(i)) { RegularBlockImpl b = new RegularBlockImpl(); block.setSuccessor(b); block = b; } block.addNode(node.getNode()); node.setBlock(block); // does this node end the execution (modeled as an edge to // the exceptional exit block) boolean terminatesExecution = node.getTerminatesExecution(); if (terminatesExecution) { block.setSuccessor(exceptionalExitBlock); block = new RegularBlockImpl(); } break; case CONDITIONAL_JUMP: { ConditionalJump cj = (ConditionalJump) node; // Exception nodes may fall through to conditional jumps, // so we set the block which is required for the insertion // of missing edges. node.setBlock(block); assert block != null; final ConditionalBlockImpl cb = new ConditionalBlockImpl(); if (cj.getTrueFlowRule() != null) { cb.setThenFlowRule(cj.getTrueFlowRule()); } if (cj.getFalseFlowRule() != null) { cb.setElseFlowRule(cj.getFalseFlowRule()); } block.setSuccessor(cb); block = new RegularBlockImpl(); // use two anonymous SingleSuccessorBlockImpl that set the // 'then' and 'else' successor of the conditional block final Label thenLabel = cj.getThenLabel(); final Label elseLabel = cj.getElseLabel(); missingEdges.add( new Tuple<>( new SingleSuccessorBlockImpl(BlockType.REGULAR_BLOCK) { @Override public void setSuccessor(BlockImpl successor) { cb.setThenSuccessor(successor); } }, bindings.get(thenLabel))); missingEdges.add( new Tuple<>( new SingleSuccessorBlockImpl(BlockType.REGULAR_BLOCK) { @Override public void setSuccessor(BlockImpl successor) { cb.setElseSuccessor(successor); } }, bindings.get(elseLabel))); break; } case UNCONDITIONAL_JUMP: if (leaders.contains(i)) { RegularBlockImpl b = new RegularBlockImpl(); block.setSuccessor(b); block = b; } node.setBlock(block); if (node.getLabel() == regularExitLabel) { block.setSuccessor(regularExitBlock); } else if (node.getLabel() == exceptionalExitLabel) { block.setSuccessor(exceptionalExitBlock); } else { missingEdges.add(new Tuple<>(block, bindings.get(node.getLabel()))); } block = new RegularBlockImpl(); break; case EXCEPTION_NODE: NodeWithExceptionsHolder en = (NodeWithExceptionsHolder) node; // create new exception block and link with previous block ExceptionBlockImpl e = new ExceptionBlockImpl(); Node nn = en.getNode(); e.setNode(nn); node.setBlock(e); block.setSuccessor(e); block = new RegularBlockImpl(); // ensure linking between e and next block (normal edge) // Note: do not link to the next block for throw statements // (these throw exceptions for sure) if (!node.getTerminatesExecution()) { missingEdges.add(new Tuple<>(e, i + 1)); } // exceptional edges for (Entry> entry : en.getExceptions().entrySet()) { TypeMirror cause = entry.getKey(); for (Label label : entry.getValue()) { Integer target = bindings.get(label); missingExceptionalEdges.add( new Tuple( e, target, cause)); } } break; } i++; } // add missing edges for (Tuple p : missingEdges) { Integer index = p.b; ExtendedNode extendedNode = nodeList.get(index); BlockImpl target = extendedNode.getBlock(); SingleSuccessorBlockImpl source = p.a; source.setSuccessor(target); } // add missing exceptional edges for (Tuple p : missingExceptionalEdges) { Integer index = p.b; TypeMirror cause = (TypeMirror) p.c; ExceptionBlockImpl source = p.a; if (index == null) { // edge to exceptional exit source.addExceptionalSuccessor(exceptionalExitBlock, cause); } else { // edge to specific target ExtendedNode extendedNode = nodeList.get(index); BlockImpl target = extendedNode.getBlock(); source.addExceptionalSuccessor(target, cause); } } return new ControlFlowGraph( startBlock, regularExitBlock, exceptionalExitBlock, in.underlyingAST, in.treeLookupMap, in.convertedTreeLookupMap, in.unaryAssignNodeLookupMap, in.returnNodes, in.generatedTreesLookupMap); } } /* --------------------------------------------------------- */ /* Phase One */ /* --------------------------------------------------------- */ /** * A wrapper object to pass around the result of phase one. For a documentation of the fields * see {@link CFGTranslationPhaseOne}. */ protected static class PhaseOneResult { private final IdentityHashMap treeLookupMap; private final IdentityHashMap convertedTreeLookupMap; private final IdentityHashMap unaryAssignNodeLookupMap; private final UnderlyingAST underlyingAST; private final Map bindings; private final ArrayList nodeList; private final Set leaders; private final List returnNodes; private final IdentityHashMap> generatedTreesLookupMap; public PhaseOneResult( UnderlyingAST underlyingAST, IdentityHashMap treeLookupMap, IdentityHashMap convertedTreeLookupMap, IdentityHashMap unaryAssignNodeLookupMap, ArrayList nodeList, Map bindings, Set leaders, List returnNodes, IdentityHashMap> generatedTreesLookupMap) { this.underlyingAST = underlyingAST; this.treeLookupMap = treeLookupMap; this.convertedTreeLookupMap = convertedTreeLookupMap; this.unaryAssignNodeLookupMap = unaryAssignNodeLookupMap; this.nodeList = nodeList; this.bindings = bindings; this.leaders = leaders; this.returnNodes = returnNodes; this.generatedTreesLookupMap = generatedTreesLookupMap; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (ExtendedNode n : nodeList) { sb.append(nodeToString(n)); sb.append("\n"); } return sb.toString(); } protected String nodeToString(ExtendedNode n) { if (n.getType() == ExtendedNodeType.CONDITIONAL_JUMP) { ConditionalJump t = (ConditionalJump) n; return "TwoTargetConditionalJump(" + resolveLabel(t.getThenLabel()) + "," + resolveLabel(t.getElseLabel()) + ")"; } else if (n.getType() == ExtendedNodeType.UNCONDITIONAL_JUMP) { return "UnconditionalJump(" + resolveLabel(n.getLabel()) + ")"; } else { return n.toString(); } } private String resolveLabel(Label label) { Integer index = bindings.get(label); if (index == null) { return "null"; } return nodeToString(nodeList.get(index)); } } /** * 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 lookup map that gives the 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 * no node is returned (rather, 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). */ public class CFGTranslationPhaseOne extends TreePathScanner { public CFGTranslationPhaseOne() {} /** Annotation processing environment and its associated type and tree utilities. */ protected ProcessingEnvironment env; protected Elements elements; protected Types types; protected Trees trees; protected TreeBuilder treeBuilder; protected AnnotationProvider annotationProvider; /** * Current {@link Label} to which a break statement with no label should jump, or null if * there is no valid destination. */ protected /*@Nullable*/ Label breakTargetL; /** * 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. */ protected Map breakLabels; /** * Current {@link Label} to which a continue statement with no label should jump, or null if * there is no valid destination. */ protected /*@Nullable*/ Label continueTargetL; /** * 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. */ protected Map continueLabels; /** * Maps from AST {@link Tree}s to {@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 treeLookupMap, while the Node for the post-conversion value is stored in the * convertedTreeLookupMap. */ protected IdentityHashMap treeLookupMap; /** Map from AST {@link Tree}s to post-conversion {@link Node}s. */ protected IdentityHashMap convertedTreeLookupMap; /** Map from AST {@link UnaryTree}s to compound {@link AssignmentNode}s. */ protected IdentityHashMap unaryAssignNodeLookupMap; /** The list of extended nodes. */ protected ArrayList nodeList; /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ protected Map bindings; /** The set of leaders (represented as indices into {@code nodeList}). */ protected Set leaders; /** * All return nodes (if any) encountered. Only includes return statements that actually * return something */ private List returnNodes; /** Map from AST {@link Tree}s to generated {@link Tree}s. */ protected IdentityHashMap> generatedTreesLookupMap; /** Nested scopes of try-catch blocks in force at the current program point. */ private TryStack tryStack; /** * Performs the actual work of phase one. * * @param bodyPath path to the body of the underlying AST's method * @param env annotation processing environment containing type utilities * @param underlyingAST the AST for which the CFG is to be built * @param exceptionalExitLabel the label for exceptional exits from the CFG * @param treeBuilder builder for new AST nodes * @param annotationProvider extracts annotations from AST nodes * @return the result of phase one */ public PhaseOneResult process( TreePath bodyPath, ProcessingEnvironment env, UnderlyingAST underlyingAST, Label exceptionalExitLabel, TreeBuilder treeBuilder, AnnotationProvider annotationProvider) { this.env = env; this.tryStack = new TryStack(exceptionalExitLabel); this.treeBuilder = treeBuilder; this.annotationProvider = annotationProvider; elements = env.getElementUtils(); types = env.getTypeUtils(); if (trees == null) { trees = Trees.instance(env); } // initialize lists and maps treeLookupMap = new IdentityHashMap<>(); convertedTreeLookupMap = new IdentityHashMap<>(); unaryAssignNodeLookupMap = new IdentityHashMap<>(); nodeList = new ArrayList<>(); bindings = new HashMap<>(); leaders = new HashSet<>(); breakLabels = new HashMap<>(); continueLabels = new HashMap<>(); returnNodes = new ArrayList<>(); generatedTreesLookupMap = new IdentityHashMap<>(); // traverse AST of the method body Node finalNode = scan(bodyPath, 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, env.getTypeUtils()); 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 degenerated control graph case that will be // removed in a later phase. nodeList.add(new UnconditionalJump(regularExitLabel)); return new PhaseOneResult( underlyingAST, treeLookupMap, convertedTreeLookupMap, unaryAssignNodeLookupMap, nodeList, bindings, leaders, returnNodes, generatedTreesLookupMap); } public PhaseOneResult process( CompilationUnitTree root, ProcessingEnvironment env, UnderlyingAST underlyingAST, Label exceptionalExitLabel, TreeBuilder treeBuilder, AnnotationProvider annotationProvider) { trees = Trees.instance(env); TreePath bodyPath = trees.getPath(root, underlyingAST.getCode()); assert bodyPath != null; return process( bodyPath, env, underlyingAST, exceptionalExitLabel, treeBuilder, annotationProvider); } /** * 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) {} /* --------------------------------------------------------- */ /* 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; } if (!treeLookupMap.containsKey(tree)) { treeLookupMap.put(tree, node); } Tree enclosingParens = parenMapping.get(tree); while (enclosingParens != null) { treeLookupMap.put(enclosingParens, 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 treeLookupMap.containsKey(tree); convertedTreeLookupMap.put(tree, node); } /** * Add a unary tree in the compound assign lookup map. This method is used to update the * UnaryTree-AssignmentNode mapping with compound assign nodes. * * @param tree the tree used as a key in the map * @param unaryAssignNode the node to add to the lookup map */ protected void addToUnaryAssignLookupMap(UnaryTree tree, AssignmentNode unaryAssignNode) { unaryAssignNodeLookupMap.put(tree, unaryAssignNode); } /** * Add a tree in the generated-trees lookup map. * * @param tree the tree used as a key in the map * @param generatedTree the tree to add to the lookup map */ protected void addToGeneratedTreesLookupMap(Tree tree, Tree generatedTree) { assert tree != null; assert generatedTree != null; if (!generatedTreesLookupMap.containsKey(tree)) { generatedTreesLookupMap.put(tree, new ArrayList()); } generatedTreesLookupMap.get(tree).add(generatedTree); } /** * Extend the list of extended nodes with a node. * * @param node the node to add * @return the same node (for convenience) */ protected T extendWithNode(T node) { addToLookupMap(node); extendWithExtendedNode(new NodeHolder(node)); return 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 * exception 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 HashMap<>(); for (TypeMirror cause : causes) { exceptions.put(cause, tryStack.possibleLabels(cause)); } NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); extendWithExtendedNode(exNode); return exNode; } /** * 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 exception {@code cause} 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 HashMap<>(); 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 */ protected void insertExtendedNodeAfter(ExtendedNode n, 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 (Entry e : bindings.entrySet()) { if (e.getValue() >= index + 1) { bindings.put(e.getKey(), e.getValue() + 1); } } // update leaders Set newLeaders = new HashSet<>(); for (Integer l : leaders) { if (l >= index + 1) { newLeaders.add(l + 1); } else { newLeaders.add(l); } } leaders = newLeaders; } else { nodeList.add(n); } } /** * Add the label {@code l} to the extended node that will be placed next in the sequence. */ protected void addLabelForNextNode(Label l) { leaders.add(nodeList.size()); bindings.put(l, nodeList.size()); } /* --------------------------------------------------------- */ /* Utility Methods */ /* --------------------------------------------------------- */ protected long uid = 0; 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); 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 TypeElement throwableElement = elements.getTypeElement("java.lang.Throwable"); addToConvertedLookupMap(node.getTree(), boxed); insertNodeWithExceptionsAfter( boxed, Collections.singleton(throwableElement.asType()), 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 TypeElement npeElement = elements.getTypeElement("java.lang.NullPointerException"); insertNodeWithExceptionsAfter( primValueAccess, Collections.singleton(npeElement.asType()), 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 TypeElement throwableElement = elements.getTypeElement("java.lang.Throwable"); addToConvertedLookupMap(node.getTree(), unboxed); insertNodeWithExceptionsAfter( unboxed, Collections.singleton(throwableElement.asType()), primValueAccess); return unboxed; } else { return node; } } private TreeInfo getTreeInfo(Tree tree) { final TypeMirror type = TreeUtils.typeOf(tree); final boolean boxed = TypesUtils.isBoxedPrimitive(type); final TypeMirror unboxedType = boxed ? types.unboxedType(type) : type; final boolean bool = TypesUtils.isBooleanType(type); final 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; } }; } /** @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 TypeElement stringElement = elements.getTypeElement("java.lang.String"); if (!TypesUtils.isString(node.getType())) { Node converted = new StringConversionNode(node.getTree(), node, stringElement.asType()); 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, which may 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 = TypesUtils.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); nodeType = node.getType(); } 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); nodeType = node.getType(); } else { node = box(node); nodeType = node.getType(); } } else if (isRightBoxed && isLeftPrimitive) { node = unbox(node); nodeType = node.getType(); if (types.isSubtype(nodeType, varType) && !types.isSameType(nodeType, varType)) { node = widen(node, varType); nodeType = node.getType(); } } else if (isRightPrimitive && isLeftPrimitive) { if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { node = narrow(node, varType); 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 and as 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. * * @param method an ExecutableElement representing a method to be called * @param actualExprs a List of argument expressions to a call * @return a List of {@link Node}s representing arguments after conversions required by a * call to this method */ protected List convertCallArguments( ExecutableElement method, List actualExprs) { // 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 = method.getParameters(); ArrayList convertedNodes = new ArrayList(); int numFormals = formals.size(); int numActuals = actualExprs.size(); if (method.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).asType(); List dimensions = new ArrayList<>(); List initializers = new ArrayList<>(); 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); convertedNodes.add( methodInvocationConvert(actualVal, formals.get(i).asType())); } } else { assert lastParamType instanceof ArrayType : "variable argument formal must be an array"; // 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); convertedNodes.add( methodInvocationConvert(actualVal, formals.get(i).asType())); } List inits = new ArrayList(); TypeMirror elemType = ((ArrayType) lastParamType).getComponentType(); 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, initializers); extendWithNode(lastArgument); convertedNodes.add(lastArgument); } } else { for (int i = 0; i < numActuals; i++) { Node actualVal = scan(actualExprs.get(i), null); convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i).asType())); } } return convertedNodes; } /** * 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) { assert false : "AnnotationTree is unexpected in AST to CFG translation"; return null; } @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 variable // arguments 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; } // TODO? Variable wasn't used. // boolean isBooleanMethod = TypesUtils.isBooleanType(method.getReturnType()); 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, TreeUtils.enclosingClass(getCurrentPath())); MethodAccessNode target = new MethodAccessNode(methodSelect, receiver); ExecutableElement element = TreeUtils.elementFromUse(tree); if (ElementUtils.isStatic(element) || receiver instanceof ThisLiteralNode) { // No NullPointerException can be thrown, use normal node extendWithNode(target); } else { TypeElement npeElement = elements.getTypeElement("java.lang.NullPointerException"); extendWithNodeWithException(target, npeElement.asType()); } List arguments = new ArrayList<>(); // 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 if (!TreeUtils.isEnumSuper(tree)) { arguments = convertCallArguments(method, actualExprs); } // TODO: lock the receiver for synchronized methods MethodInvocationNode node = new MethodInvocationNode(tree, target, arguments, getCurrentPath()); Set thrownSet = new HashSet<>(); // Add exceptions explicitly mentioned in the throws clause. List thrownTypes = element.getThrownTypes(); thrownSet.addAll(thrownTypes); // Add Throwable to account for unchecked exceptions TypeElement throwableElement = elements.getTypeElement("java.lang.Throwable"); thrownSet.add(throwableElement.asType()); ExtendedNode extendedNode = extendWithNodeWithExceptions(node, thrownSet); /* Check for the TerminatesExecution annotation. */ Element methodElement = TreeUtils.elementFromTree(tree); boolean terminatesExecution = annotationProvider.getDeclAnnotation(methodElement, TerminatesExecution.class) != null; if (terminatesExecution) { extendedNode.setTerminatesExecution(true); } return node; } @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); } 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 = TreeUtils.enclosingMethod(getCurrentPath()); if (enclosingMethod != null) { return TreeUtils.elementFromDeclaration(enclosingMethod); } else { ClassTree enclosingClass = TreeUtils.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); } TypeElement assertException = elements.getTypeElement("java.lang.AssertionError"); AssertionErrorNode assertNode = new AssertionErrorNode(tree, condition, detail, assertException.asType()); extendWithNode(assertNode); NodeWithExceptionsHolder exNode = extendWithNodeWithException( new ThrowNode(null, assertNode, env.getTypeUtils()), assertException.asType()); exNode.setTerminatesExecution(true); // then branch (nothing happens) addLabelForNextNode(assertEnd); } @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: field access if (TreeUtils.isFieldAccess(variable)) { // visit receiver Node receiver = getReceiver(variable, TreeUtils.enclosingClass(getCurrentPath())); // 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 ThisLiteralNode) { // No NullPointerException can be thrown, use normal node extendWithNode(target); } else { TypeElement npeElement = elements.getTypeElement("java.lang.NullPointerException"); extendWithNodeWithException(target, npeElement.asType()); } // add assignment node assignmentNode = new AssignmentNode(tree, target, expression); extendWithNode(assignmentNode); } // case 2: other cases 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 access tree containing the receiver * @param classTree the ClassTree enclosing the field access * @return the receiver of the field access */ private Node getReceiver(ExpressionTree tree, ClassTree classTree) { assert TreeUtils.isFieldAccess(tree) || TreeUtils.isMethodAccess(tree); if (tree.getKind().equals(Tree.Kind.MEMBER_SELECT)) { MemberSelectTree mtree = (MemberSelectTree) tree; return scan(mtree.getExpression(), null); } else { Element ele = TreeUtils.elementFromUse(tree); TypeElement declaringClass = ElementUtils.enclosingClass(ele); TypeMirror type = ElementUtils.getType(declaringClass); if (ElementUtils.isStatic(ele)) { Node node = new ClassNameNode(type, declaringClass); extendWithNode(node); return node; } else { Node node = new ImplicitThisLiteralNode(type); 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.isIntegral(exprType)) { operNode = new IntegerDivisionNode(operTree, targetRHS, value); TypeElement throwableElement = elements.getTypeElement("java.lang.ArithmeticException"); extendWithNodeWithException(operNode, throwableElement.asType()); } else { operNode = new FloatingDivisionNode(operTree, targetRHS, value); } } else { assert kind == Kind.REMAINDER_ASSIGNMENT; if (TypesUtils.isIntegral(exprType)) { operNode = new IntegerRemainderNode(operTree, targetRHS, value); TypeElement throwableElement = elements.getTypeElement("java.lang.ArithmeticException"); extendWithNodeWithException(operNode, throwableElement.asType()); } else { operNode = new FloatingRemainderNode(operTree, targetRHS, value); } } extendWithNode(operNode); TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree); handleArtificialTree(castTree); TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType); 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); Node r = new StringConcatenateAssignmentNode(tree, targetRHS, value); extendWithNode(r); return r; } 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 == Kind.MINUS_ASSIGNMENT; operNode = new NumericalSubtractionNode(operTree, targetRHS, value); } extendWithNode(operNode); TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree); handleArtificialTree(castTree); TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType); 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 == Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT; operNode = new UnsignedRightShiftNode(operTree, targetRHS, value); } extendWithNode(operNode); TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree); handleArtificialTree(castTree); TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType); 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 { assert false : "Both argument 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 == Kind.XOR_ASSIGNMENT; operNode = new BitwiseXorNode(operTree, targetRHS, value); } extendWithNode(operNode); TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree); handleArtificialTree(castTree); TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType); castNode.setInSource(false); extendWithNode(castNode); AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); extendWithNode(assignNode); return assignNode; default: assert false : "unexpected compound assignment type"; break; } assert false : "unexpected compound assignment type"; return null; } @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.isIntegral(exprType)) { r = new IntegerDivisionNode(tree, left, right); TypeElement throwableElement = elements.getTypeElement("java.lang.ArithmeticException"); extendWithNodeWithException(r, throwableElement.asType()); } else { r = new FloatingDivisionNode(tree, left, right); } } else { assert kind == Kind.REMAINDER; if (TypesUtils.isIntegral(exprType)) { r = new IntegerRemainderNode(tree, left, right); TypeElement throwableElement = elements.getTypeElement("java.lang.ArithmeticException"); extendWithNodeWithException(r, throwableElement.asType()); } 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 == 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 == 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 == 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 == 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 rightStartL = new Label(); Label shortCircuitL = new Label(); // left-hand side Node left = scan(leftTree, p); ConditionalJump cjump; if (kind == Tree.Kind.CONDITIONAL_AND) { cjump = new ConditionalJump(rightStartL, shortCircuitL); cjump.setFalseFlowRule(Store.FlowRule.ELSE_TO_ELSE); } else { cjump = new ConditionalJump(shortCircuitL, rightStartL); cjump.setTrueFlowRule(Store.FlowRule.THEN_TO_THEN); } extendWithExtendedNode(cjump); // right-hand side addLabelForNextNode(rightStartL); Node right = scan(rightTree, p); // conditional expression itself addLabelForNextNode(shortCircuitL); 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: assert false : "unexpected binary tree: " + kind; break; } assert r != null : "unexpected binary tree"; return extendWithNode(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 breakTargetL != null : "no target for break statement"; extendWithExtendedNode(new UnconditionalJump(breakTargetL)); } else { assert breakLabels.containsKey(label); extendWithExtendedNode(new UnconditionalJump(breakLabels.get(label))); } return null; } @Override public Node visitSwitch(SwitchTree tree, Void p) { SwitchBuilder builder = new SwitchBuilder(tree, p); builder.build(); return null; } private class SwitchBuilder { private final SwitchTree switchTree; private final Label[] caseBodyLabels; private final Void p; private Node switchExpr; private SwitchBuilder(SwitchTree tree, Void p) { this.switchTree = tree; this.caseBodyLabels = new Label[switchTree.getCases().size() + 1]; this.p = p; } public void build() { Label oldBreakTargetL = breakTargetL; breakTargetL = new Label(); int cases = caseBodyLabels.length - 1; for (int i = 0; i < cases; ++i) { caseBodyLabels[i] = new Label(); } caseBodyLabels[cases] = breakTargetL; TypeMirror switchExprType = TreeUtils.typeOf(switchTree.getExpression()); VariableTree variable = treeBuilder.buildVariableDecl( switchExprType, uniqueName("switch"), findOwner(), null); handleArtificialTree(variable); VariableDeclarationNode variableNode = new VariableDeclarationNode(variable); variableNode.setInSource(false); extendWithNode(variableNode); ExpressionTree variableUse = treeBuilder.buildVariableUse(variable); handleArtificialTree(variable); LocalVariableNode variableUseNode = new LocalVariableNode(variableUse); variableUseNode.setInSource(false); extendWithNode(variableUseNode); Node switchExprNode = unbox(scan(switchTree.getExpression(), p)); AssignmentTree assign = treeBuilder.buildAssignment(variableUse, switchTree.getExpression()); handleArtificialTree(assign); switchExpr = new AssignmentNode(assign, variableUseNode, switchExprNode); switchExpr.setInSource(false); extendWithNode(switchExpr); extendWithNode( new MarkerNode( switchTree, "start of switch statement", env.getTypeUtils())); Integer defaultIndex = null; for (int i = 0; i < cases; ++i) { CaseTree caseTree = switchTree.getCases().get(i); if (caseTree.getExpression() == null) { defaultIndex = i; } else { buildCase(caseTree, i); } } if (defaultIndex != null) { // 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(switchTree.getCases().get(defaultIndex), defaultIndex); } addLabelForNextNode(breakTargetL); breakTargetL = oldBreakTargetL; } private void buildCase(CaseTree tree, int index) { final Label thisBodyL = caseBodyLabels[index]; final Label nextBodyL = caseBodyLabels[index + 1]; final Label nextCaseL = new Label(); ExpressionTree exprTree = tree.getExpression(); if (exprTree != null) { Node expr = scan(exprTree, p); CaseNode test = new CaseNode(tree, switchExpr, expr, env.getTypeUtils()); extendWithNode(test); extendWithExtendedNode(new ConditionalJump(thisBodyL, nextCaseL)); } addLabelForNextNode(thisBodyL); for (StatementTree stmt : tree.getStatements()) { scan(stmt, p); } extendWithExtendedNode(new UnconditionalJump(nextBodyL)); addLabelForNextNode(nextCaseL); } } @Override public Node visitCase(CaseTree tree, Void p) { 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; } @Override public Node visitClass(ClassTree tree, Void p) { declaredClasses.add(tree); return null; } @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(); Node condition = unbox(scan(tree.getCondition(), p)); ConditionalJump cjump = new ConditionalJump(trueStart, falseStart); extendWithExtendedNode(cjump); addLabelForNextNode(trueStart); Node trueExpr = scan(tree.getTrueExpression(), p); trueExpr = conditionalExprPromotion(trueExpr, exprType); extendWithExtendedNode(new UnconditionalJump(merge)); addLabelForNextNode(falseStart); Node falseExpr = scan(tree.getFalseExpression(), p); falseExpr = conditionalExprPromotion(falseExpr, exprType); addLabelForNextNode(merge); Node node = new TernaryExpressionNode(tree, condition, trueExpr, falseExpr); extendWithNode(node); return node; } @Override public Node visitContinue(ContinueTree tree, Void p) { Name label = tree.getLabel(); if (label == null) { assert continueTargetL != null : "no target for continue statement"; extendWithExtendedNode(new UnconditionalJump(continueTargetL)); } 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(); } Label oldBreakTargetL = breakTargetL; breakTargetL = loopExit; Label oldContinueTargetL = continueTargetL; continueTargetL = conditionStart; // Loop body addLabelForNextNode(loopEntry); if (tree.getStatement() != null) { scan(tree.getStatement(), p); } // Condition addLabelForNextNode(conditionStart); if (tree.getCondition() != null) { unbox(scan(tree.getCondition(), p)); ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); extendWithExtendedNode(cjump); } // Loop exit addLabelForNextNode(loopExit); breakTargetL = oldBreakTargetL; continueTargetL = oldContinueTargetL; return null; } @Override public Node visitErroneous(ErroneousTree tree, Void p) { assert false : "ErroneousTree is unexpected in AST to CFG translation"; return null; } @Override public Node visitExpressionStatement(ExpressionStatementTree tree, Void p) { return scan(tree.getExpression(), p); } @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(); } Label oldBreakTargetL = breakTargetL; breakTargetL = loopExit; Label oldContinueTargetL = continueTargetL; continueTargetL = updateStart; // Distinguish loops over Iterables from loops over arrays. TypeElement iterableElement = elements.getTypeElement("java.lang.Iterable"); TypeMirror iterableType = types.erasure(iterableElement.asType()); 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()); nextCallNode.setInSource(false); extendWithNode(nextCallNode); translateAssignment(variable, new LocalVariableNode(variable), nextCall); if (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 = extendWithNode(new IntegerLiteralNode(zero)); 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 = extendWithNode(new LocalVariableNode(arrayUse1)); 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.setInSource(false); extendWithNode(arrayAccessNode); translateAssignment(variable, new LocalVariableNode(variable), arrayAccessNode); Element npeElement = elements.getTypeElement("java.lang.NullPointerException"); extendWithNodeWithException(arrayAccessNode, npeElement.asType()); if (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); breakTargetL = oldBreakTargetL; continueTargetL = oldContinueTargetL; 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(); } Label oldBreakTargetL = breakTargetL; breakTargetL = loopExit; Label oldContinueTargetL = continueTargetL; continueTargetL = 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); if (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); breakTargetL = oldBreakTargetL; continueTargetL = oldContinueTargetL; return null; } @Override public Node visitIdentifier(IdentifierTree tree, Void p) { Node node; if (TreeUtils.isFieldAccess(tree)) { Node receiver = getReceiver(tree, TreeUtils.enclosingClass(getCurrentPath())); node = new FieldAccessNode(tree, receiver); } else { Element element = TreeUtils.elementFromUse(tree); switch (element.getKind()) { case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: case TYPE_PARAMETER: node = new ClassNameNode(tree); break; case FIELD: // Note that "this"/"super" is a field, but not a field access. if (element.getSimpleName().contentEquals("this")) { node = new ExplicitThisLiteralNode(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: throw new IllegalArgumentException( "unexpected element kind : " + element.getKind()); } } 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) { assert false : "ImportTree is unexpected in AST to CFG translation"; return null; } @Override public Node visitArrayAccess(ArrayAccessTree tree, Void p) { Node array = scan(tree.getExpression(), p); Node index = unaryNumericPromotion(scan(tree.getIndex(), p)); Node arrayAccess = extendWithNode(new ArrayAccessNode(tree, array, index)); Element aioobeElement = elements.getTypeElement("java.lang.ArrayIndexOutOfBoundsException"); extendWithNodeWithException(arrayAccess, aioobeElement.asType()); Element npeElement = elements.getTypeElement("java.lang.NullPointerException"); extendWithNodeWithException(arrayAccess, npeElement.asType()); 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 breakL = new Label(labelName + "_break"); Label continueL = new Label(labelName + "_continue"); breakLabels.put(labelName, breakL); continueLabels.put(labelName, continueL); scan(tree.getStatement(), p); addLabelForNextNode(breakL); 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: assert false : "unexpected literal tree"; break; } assert r != null : "unexpected literal tree"; Node result = extendWithNode(r); return result; } @Override public Node visitMethod(MethodTree tree, Void p) { assert false : "MethodTree is unexpected in AST to CFG translation"; return null; } @Override public Node visitModifiers(ModifiersTree tree, Void p) { assert false : "ModifiersTree is unexpected in AST to CFG translation"; return null; } @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(); List dimensionNodes = new ArrayList(); if (dimensions != null) { for (ExpressionTree dim : dimensions) { dimensionNodes.add(unaryNumericPromotion(scan(dim, p))); } } List initializerNodes = new ArrayList(); if (initializers != null) { for (ExpressionTree init : initializers) { initializerNodes.add(assignConvert(scan(init, p), elemType)); } } Node node = new ArrayCreationNode(tree, type, dimensionNodes, initializerNodes); return extendWithNode(node); } @Override public Node visitNewClass(NewClassTree tree, Void p) { // see JLS 15.9 Tree enclosingExpr = tree.getEnclosingExpression(); if (enclosingExpr != null) { scan(enclosingExpr, p); } // We ignore any class body because its methods should // be visited separately. // TODO: For anonymous classes we want to propagate the current store // to the anonymous class. // See Issues 266, 811. // Convert constructor arguments ExecutableElement constructor = TreeUtils.elementFromUse(tree); List actualExprs = tree.getArguments(); List arguments = convertCallArguments(constructor, actualExprs); // TODO: for anonymous classes, don't use the identifier alone. // See Issue 890. Node constructorNode = scan(tree.getIdentifier(), p); Node node = new ObjectCreationNode(tree, constructorNode, arguments); Set thrownSet = new HashSet<>(); // Add exceptions explicitly mentioned in the throws clause. List thrownTypes = constructor.getThrownTypes(); thrownSet.addAll(thrownTypes); // Add Throwable to account for unchecked exceptions TypeElement throwableElement = elements.getTypeElement("java.lang.Throwable"); thrownSet.add(throwableElement.asType()); extendWithNodeWithExceptions(node, thrownSet); return node; } /** * Maps a {@code Tree} 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); Tree enclosing = TreeUtils.enclosingOfKind( getCurrentPath(), new HashSet( Arrays.asList(Kind.METHOD, Kind.LAMBDA_EXPRESSION))); if (enclosing.getKind() == Kind.LAMBDA_EXPRESSION) { LambdaExpressionTree lambdaTree = (LambdaExpressionTree) enclosing; TreePath lambdaTreePath = TreePath.getPath(getCurrentPath().getCompilationUnit(), lambdaTree); Context ctx = ((JavacProcessingEnvironment) env).getContext(); Element overriddenElement = com.sun.tools.javac.code.Types.instance(ctx) .findDescriptorSymbol( ((Type) trees.getTypeMirror(lambdaTreePath)).tsym); result = new ReturnNode( tree, node, env.getTypeUtils(), lambdaTree, (MethodSymbol) overriddenElement); } else { result = new ReturnNode(tree, node, env.getTypeUtils(), (MethodTree) enclosing); } returnNodes.add(result); extendWithNode(result); } extendWithExtendedNode(new UnconditionalJump(regularExitLabel)); // TODO: return statements should also flow to an enclosing finally block 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 Node result = null; Element element = TreeUtils.elementFromUse(tree); switch (element.getKind()) { case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: result = extendWithNode(new ClassNameNode(tree, expr)); break; case PACKAGE: result = extendWithNode(new PackageNameNode(tree, (PackageNameNode) expr)); break; default: assert false : "Unexpected element kind: " + element.getKind(); return null; } return result; } Node node = new FieldAccessNode(tree, expr); Element element = TreeUtils.elementFromUse(tree); if (ElementUtils.isStatic(element) || expr instanceof ImplicitThisLiteralNode || expr instanceof ExplicitThisLiteralNode) { // No NullPointerException can be thrown, use normal node extendWithNode(node); } else { TypeElement npeElement = elements.getTypeElement("java.lang.NullPointerException"); extendWithNodeWithException(node, npeElement.asType()); } 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) { assert false : "CompilationUnitTree is unexpected in AST to CFG translation"; return null; } @Override public Node visitTry(TryTree tree, Void p) { List catches = tree.getCatches(); BlockTree finallyBlock = tree.getFinallyBlock(); extendWithNode(new MarkerNode(tree, "start of try statement", env.getTypeUtils())); // TODO: Should we handle try-with-resources blocks by also generating code // for automatically closing the resources? List resources = tree.getResources(); for (Tree resource : resources) { scan(resource, p); } List> catchLabels = new ArrayList<>(); for (CatchTree c : catches) { TypeMirror type = TreeUtils.typeOf(c.getParameter().getType()); assert type != null : "exception parameters must have a type"; catchLabels.add(Pair.of(type, new Label())); } Label finallyLabel = null; // Label exceptionalFinallyLabel = null; // #293 if (finallyBlock != null) { finallyLabel = new Label(); // exceptionalFinallyLabel = new Label(); // #293 // tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); tryStack.pushFrame(new TryFinallyFrame(finallyLabel)); } Label doneLabel = new Label(); tryStack.pushFrame(new TryCatchFrame(types, catchLabels)); scan(tree.getBlock(), p); extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); tryStack.popFrame(); int catchIndex = 0; for (CatchTree c : catches) { addLabelForNextNode(catchLabels.get(catchIndex).second); scan(c, p); catchIndex++; extendWithExtendedNode( new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); } if (finallyLabel != null) { tryStack.popFrame(); // if (hasExceptionalPath(exceptionalFinallyLabel)) { // #293 // 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. // addLabelForNextNode(exceptionalFinallyLabel); addLabelForNextNode(finallyLabel); scan(finallyBlock, p); TypeMirror throwableType = elements.getTypeElement("java.lang.Throwable").asType(); NodeWithExceptionsHolder throwing = extendWithNodeWithException( new MarkerNode(tree, "end of finally block", env.getTypeUtils()), throwableType); /* #293 throwing.setTerminatesExecution(true); addLabelForNextNode(finallyLabel); BlockTree successfulFinallyBlock = treeBuilder.copy(finallyBlock); addToGeneratedTreesLookupMap(finallyBlock, successfulFinallyBlock); scan(successfulFinallyBlock, p); extendWithNode( new MarkerNode(tree, "end of finally block", env.getTypeUtils())); extendWithExtendedNode(new UnconditionalJump(doneLabel)); } else { // Scan 'finallyBlock' for only 'finallyLabel' (a successful path) // because there is no path to 'exceptionalFinallyLabel'. addLabelForNextNode(finallyLabel); scan(finallyBlock, p); extendWithNode( new MarkerNode(tree, "end of finally block", env.getTypeUtils())); extendWithExtendedNode(new UnconditionalJump(doneLabel)); }*/ } addLabelForNextNode(doneLabel); return null; } /** * 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





© 2015 - 2025 Weber Informatics LLC | Privacy Policy