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

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

Go to download

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

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

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

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.ExceptionBlockImpl;
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.*;
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.InternalUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.trees.TreeBuilder;

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.LinkedList;
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 com.sun.source.tree.*;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
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;

/**
 * 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. *
  3. 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.
  4. *
  5. 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.
  6. *
* * @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 LinkedList<>(); 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 LinkedList<>(); 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 static abstract 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. *
  3. 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.
  4. *
  5. 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.
  6. *
  7. 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.
  8. *
* * 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); } } } } return cfg; } /** * 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() { @Override public void setSuccessor(BlockImpl successor) { cb.setThenSuccessor(successor); } }, bindings.get(thenLabel))); missingEdges.add(new Tuple<>( new SingleSuccessorBlockImpl() { @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.returnNodes); } } /* --------------------------------------------------------- */ /* 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 UnderlyingAST underlyingAST; private final Map bindings; private final ArrayList nodeList; private final Set leaders; private final List returnNodes; public PhaseOneResult(UnderlyingAST underlyingAST, IdentityHashMap treeLookupMap, IdentityHashMap convertedTreeLookupMap, ArrayList nodeList, Map bindings, Set leaders, List returnNodes) { this.underlyingAST = underlyingAST; this.treeLookupMap = treeLookupMap; this.convertedTreeLookupMap = convertedTreeLookupMap; this.nodeList = nodeList; this.bindings = bindings; this.leaders = leaders; this.returnNodes = returnNodes; } @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; /** 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; /** * 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(); // initialize lists and maps treeLookupMap = new IdentityHashMap<>(); convertedTreeLookupMap = new IdentityHashMap<>(); nodeList = new ArrayList<>(); bindings = new HashMap<>(); leaders = new HashSet<>(); breakLabels = new HashMap<>(); continueLabels = new HashMap<>(); returnNodes = new ArrayList<>(); // traverse AST of the method body scan(bodyPath, null); // 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, nodeList, bindings, leaders, returnNodes); } 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()); 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); } /** * 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 * node might throw the exception 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 * node might throw any of the exception in * 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 node after pred in * the list of extended nodes, or append to the list if * 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 node that might throw the exception * cause after pred in the list of * extended nodes, or append to the list if 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 n after the node pred in the * list of extended nodes, or append n if 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 = InternalUtils.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 - 1) { // Apply method invocation conversion to all actual // arguments, then create and append an empty array for (int i = 0; i < numActuals; i++) { Node actualVal = scan(actualExprs.get(i), null); convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i).asType())); } Node lastArgument = new ArrayCreationNode(null, lastParamType, dimensions, initializers); extendWithNode(lastArgument); convertedNodes.add(lastArgument); } else { TypeMirror actualType = InternalUtils.typeOf(actualExprs .get(lastArgIndex)); if (numActuals == numFormals && types.isAssignable(actualType, 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())); } TypeMirror elemType = ((ArrayType)lastParamType).getComponentType(); for (int i = lastArgIndex; i < numActuals; i++) { Node actualVal = scan(actualExprs.get(i), null); initializers.add(assignConvert(actualVal, elemType)); } Node lastArgument = new ArrayCreationNode(null, 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); // 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 = InternalUtils.symbol(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"); MethodTree enclosingMethod = TreeUtils .enclosingMethod(getCurrentPath()); Element owner; if (enclosingMethod != null) { owner = TreeUtils.elementFromDeclaration(enclosingMethod); } else { ClassTree enclosingClass = TreeUtils.enclosingClass(getCurrentPath()); owner = TreeUtils.elementFromDeclaration(enclosingClass); } ExpressionTree initializer = null; ea = treeBuilder.buildVariableDecl( types.getPrimitiveType(TypeKind.BOOLEAN), name, owner, initializer); } return ea; } /** * 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 = InternalUtils.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 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(Tree 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 { TypeMirror classType = InternalUtils.typeOf(classTree); Node node = new ImplicitThisLiteralNode(classType); 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 = InternalUtils.typeOf(tree); TypeMirror leftType = InternalUtils.typeOf(tree.getVariable()); TypeMirror rightType = InternalUtils.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); } else { operNode = new FloatingDivisionNode(operTree, targetRHS, value); } } else { assert kind == Kind.REMAINDER_ASSIGNMENT; if (TypesUtils.isIntegral(exprType)) { operNode = new IntegerRemainderNode(operTree, targetRHS, value); } 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 = InternalUtils.typeOf(tree.getVariable()); TypeMirror rightType = InternalUtils.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 = InternalUtils.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 = InternalUtils.typeOf(tree.getVariable()); TypeMirror rightType = InternalUtils.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 = InternalUtils.typeOf(tree); TypeMirror leftType = InternalUtils.typeOf(leftTree); TypeMirror rightType = InternalUtils.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); } else { r = new FloatingDivisionNode(tree, left, right); } } else { assert kind == Kind.REMAINDER; if (TypesUtils.isIntegral(exprType)) { r = new IntegerRemainderNode(tree, left, right); } else { r = new FloatingRemainderNode(tree, left, right); } } break; } case MINUS: case PLUS: { // see JLS 15.18 // TypeMirror exprType = InternalUtils.typeOf(tree); TypeMirror leftType = InternalUtils.typeOf(leftTree); TypeMirror rightType = InternalUtils.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 = InternalUtils.typeOf(leftTree); if (TypesUtils.isBoxedPrimitive(leftType)) { leftType = types.unboxedType(leftType); } TypeMirror rightType = InternalUtils.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 = InternalUtils.typeOf(leftTree); TypeMirror rightType = InternalUtils.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 { final private SwitchTree switchTree; final private Label[] caseBodyLabels; final private 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; switchExpr = unbox(scan(switchTree.getExpression(), p)); 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 = InternalUtils.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 = InternalUtils.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(new Integer(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); 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 = InternalUtils.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 = InternalUtils.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)); return extendWithNode(new ArrayAccessNode(tree, array, index)); } @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)InternalUtils.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. // Convert constructor arguments ExecutableElement constructor = TreeUtils.elementFromUse(tree); List actualExprs = tree.getArguments(); List arguments = convertCallArguments(constructor, actualExprs); 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 Tree its directly enclosing ParenthesizedTree if one exists. * * This map is used by {@link CFGTranslationPhaseOne#addToLookupMap(Node)} to * associate a ParenthesizedTree with the dataflow Node that was used * during inference. This map is necessary because dataflow does * not create a Node for a 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 = InternalUtils.typeOf(c.getParameter().getType()); assert type != null : "exception parameters must have a type"; catchLabels.add(Pair.of(type, new Label())); } Label finallyLabel = null; if (finallyBlock != null) { finallyLabel = new Label(); 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(); addLabelForNextNode(finallyLabel); scan(finallyBlock, p); TypeMirror throwableType = elements.getTypeElement("java.lang.Throwable").asType(); extendWithNodeWithException(new MarkerNode(tree, "end of finally block", env.getTypeUtils()), throwableType); } addLabelForNextNode(doneLabel); return null; } @Override public Node visitParameterizedType(ParameterizedTypeTree tree, Void p) { return extendWithNode(new ParameterizedTypeNode(tree)); } @Override public Node visitUnionType(UnionTypeTree tree, Void p) { assert false : "UnionTypeTree is unexpected in AST to CFG translation"; return null; } @Override public Node visitArrayType(ArrayTypeTree tree, Void p) { return extendWithNode(new ArrayTypeNode(tree)); } @Override public Node visitTypeCast(TypeCastTree tree, Void p) { final Node operand = scan(tree.getExpression(), p); final TypeMirror type = InternalUtils.typeOf(tree.getType()); final Node node = new TypeCastNode(tree, operand, type); final TypeElement cceElement = elements.getTypeElement("java.lang.ClassCastException"); extendWithNodeWithException(node, cceElement.asType()); return node; } @Override public Node visitPrimitiveType(PrimitiveTypeTree tree, Void p) { return extendWithNode(new PrimitiveTypeNode(tree)); } @Override public Node visitTypeParameter(TypeParameterTree tree, Void p) { assert false : "TypeParameterTree is unexpected in AST to CFG translation"; return null; } @Override public Node visitInstanceOf(InstanceOfTree tree, Void p) { Node operand = scan(tree.getExpression(), p); TypeMirror refType = InternalUtils.typeOf(tree.getType()); InstanceOfNode node = new InstanceOfNode(tree, operand, refType, types); extendWithNode(node); return node; } @Override public Node visitUnary(UnaryTree tree, Void p) { Node result = null; Tree.Kind kind = tree.getKind(); switch (kind) { case BITWISE_COMPLEMENT: case UNARY_MINUS: case UNARY_PLUS: { // see JLS 15.14 and 15.15 Node expr = scan(tree.getExpression(), p); expr = unaryNumericPromotion(expr); // TypeMirror exprType = InternalUtils.typeOf(tree); switch (kind) { case BITWISE_COMPLEMENT: result = extendWithNode(new BitwiseComplementNode(tree, expr)); break; case UNARY_MINUS: result = extendWithNode(new NumericalMinusNode(tree, expr)); break; case UNARY_PLUS: result = extendWithNode(new NumericalPlusNode(tree, expr)); break; default: assert false; break; } break; } case LOGICAL_COMPLEMENT: { // see JLS 15.15.6 Node expr = scan(tree.getExpression(), p); result = extendWithNode(new ConditionalNotNode(tree, unbox(expr))); break; } case POSTFIX_DECREMENT: case POSTFIX_INCREMENT: { ExpressionTree exprTree = tree.getExpression(); TypeMirror exprType = InternalUtils.typeOf(exprTree); TypeMirror oneType = types.getPrimitiveType(TypeKind.INT); Node expr = scan(exprTree, p); TypeMirror promotedType = binaryPromotedType(exprType, oneType); LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); handleArtificialTree(oneTree); Node exprRHS = binaryNumericPromotion(expr, promotedType); Node one = new IntegerLiteralNode(oneTree); one.setInSource(false); extendWithNode(one); one = binaryNumericPromotion(one, promotedType); BinaryTree operTree = treeBuilder.buildBinary(promotedType, (kind == Tree.Kind.POSTFIX_INCREMENT ? Tree.Kind.PLUS : Tree.Kind.MINUS), exprTree, oneTree); handleArtificialTree(operTree); Node operNode; if (kind == Tree.Kind.POSTFIX_INCREMENT) { operNode = new NumericalAdditionNode(operTree, exprRHS, one); } else { assert kind == Tree.Kind.POSTFIX_DECREMENT; operNode = new NumericalSubtractionNode(operTree, exprRHS, one); } extendWithNode(operNode); Node narrowed = narrowAndBox(operNode, exprType); // TODO: By using the assignment as the result of the expression, we // act like a pre-increment/decrement. Fix this by saving the initial // value of the expression in a temporary. AssignmentNode assignNode = new AssignmentNode(tree, expr, narrowed); extendWithNode(assignNode); result = assignNode; break; } case PREFIX_DECREMENT: case PREFIX_INCREMENT: { ExpressionTree exprTree = tree.getExpression(); TypeMirror exprType = InternalUtils.typeOf(exprTree); TypeMirror oneType = types.getPrimitiveType(TypeKind.INT); Node expr = scan(exprTree, p); TypeMirror promotedType = binaryPromotedType(exprType, oneType); LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); handleArtificialTree(oneTree); Node exprRHS = binaryNumericPromotion(expr, promotedType); Node one = new IntegerLiteralNode(oneTree); one.setInSource(false); extendWithNode(one); one = binaryNumericPromotion(one, promotedType); BinaryTree operTree = treeBuilder.buildBinary(promotedType, (kind == Tree.Kind.PREFIX_INCREMENT ? Tree.Kind.PLUS : Tree.Kind.MINUS), exprTree, oneTree); handleArtificialTree(operTree); Node operNode; if (kind == Tree.Kind.PREFIX_INCREMENT) { operNode = new NumericalAdditionNode(operTree, exprRHS, one); } else { assert kind == Tree.Kind.PREFIX_DECREMENT; operNode = new NumericalSubtractionNode(operTree, exprRHS, one); } extendWithNode(operNode); Node narrowed = narrowAndBox(operNode, exprType); AssignmentNode assignNode = new AssignmentNode(tree, expr, narrowed); extendWithNode(assignNode); result = assignNode; break; } case OTHER: default: // special node NLLCHK if (tree.toString().startsWith("<*nullchk*>")) { Node expr = scan(tree.getExpression(), p); result = extendWithNode(new NullChkNode(tree, expr)); break; } assert false : "Unknown kind (" + kind + ") of unary expression: " + tree; } return result; } @Override public Node visitVariable(VariableTree tree, Void p) { // see JLS 14.4 boolean isField = getCurrentPath().getParentPath() != null && getCurrentPath().getParentPath().getLeaf().getKind() == Kind.CLASS; Node node = null; ClassTree enclosingClass = TreeUtils .enclosingClass(getCurrentPath()); TypeElement classElem = TreeUtils .elementFromDeclaration(enclosingClass); Node receiver = new ImplicitThisLiteralNode(classElem.asType()); if (isField) { ExpressionTree initializer = tree.getInitializer(); assert initializer != null; node = translateAssignment( tree, new FieldAccessNode(tree, TreeUtils.elementFromDeclaration(tree), receiver), initializer); } else { // local variable definition VariableDeclarationNode decl = new VariableDeclarationNode(tree); extendWithNode(decl); // initializer ExpressionTree initializer = tree.getInitializer(); if (initializer != null) { node = translateAssignment(tree, new LocalVariableNode(tree, receiver), initializer); } } return node; } @Override public Node visitWhileLoop(WhileLoopTree 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; // 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); } extendWithExtendedNode(new UnconditionalJump(conditionStart)); // Loop exit addLabelForNextNode(loopExit); breakTargetL = oldBreakTargetL; continueTargetL = oldContinueTargetL; return null; } @Override public Node visitLambdaExpression(LambdaExpressionTree tree, Void p) { declaredLambdas.add(tree); Node node = new FunctionalInterfaceNode(tree); extendWithNode(node); return node; } @Override public Node visitMemberReference(MemberReferenceTree tree, Void p) { Tree enclosingExpr = tree.getQualifierExpression(); if (enclosingExpr != null) { scan(enclosingExpr, p); } Node node = new FunctionalInterfaceNode(tree); extendWithNode(node); return node; } @Override public Node visitWildcard(WildcardTree tree, Void p) { assert false : "WildcardTree is unexpected in AST to CFG translation"; return null; } @Override public Node visitOther(Tree tree, Void p) { assert false : "Unknown AST element encountered in AST to CFG translation."; return null; } } /** * A tuple with 4 named elements. */ private interface TreeInfo { boolean isBoxed(); boolean isNumeric(); boolean isBoolean(); TypeMirror unboxedType(); } private static A firstNonNull(A first, A second) { if (first != null) { return first; } else if (second != null) { return second; } else { throw new NullPointerException(); } } /* --------------------------------------------------------- */ /* Utility routines for debugging CFG building */ /* --------------------------------------------------------- */ /** * Print a set of {@link Block}s and the edges between them. This is useful * for examining the results of phase two. */ protected static void printBlocks(Set blocks) { for (Block b : blocks) { System.out.print(b.hashCode() + ": " + b); switch (b.getType()) { case REGULAR_BLOCK: case SPECIAL_BLOCK: { Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); System.out.println(" -> " + (succ != null ? succ.hashCode() : "||")); break; } case EXCEPTION_BLOCK: { Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); System.out.print(" -> " + (succ != null ? succ.hashCode() : "||") + " {"); for (Map.Entry> entry : ((ExceptionBlockImpl) b).getExceptionalSuccessors().entrySet()) { System.out.print(entry.getKey() + " : " + entry.getValue() + ", "); } System.out.println("}"); break; } case CONDITIONAL_BLOCK: { Block tSucc = ((ConditionalBlockImpl) b).getThenSuccessor(); Block eSucc = ((ConditionalBlockImpl) b).getElseSuccessor(); System.out.println(" -> T " + (tSucc != null ? tSucc.hashCode() : "||") + " F " + (eSucc != null ? eSucc.hashCode() : "||")); break; } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy