| package org.checkerframework.dataflow.cfg.builder; |
| |
| import com.sun.source.tree.AnnotatedTypeTree; |
| import com.sun.source.tree.AnnotationTree; |
| import com.sun.source.tree.ArrayAccessTree; |
| import com.sun.source.tree.ArrayTypeTree; |
| import com.sun.source.tree.AssertTree; |
| import com.sun.source.tree.AssignmentTree; |
| import com.sun.source.tree.BinaryTree; |
| import com.sun.source.tree.BlockTree; |
| import com.sun.source.tree.BreakTree; |
| import com.sun.source.tree.CaseTree; |
| import com.sun.source.tree.CatchTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.CompilationUnitTree; |
| import com.sun.source.tree.CompoundAssignmentTree; |
| import com.sun.source.tree.ConditionalExpressionTree; |
| import com.sun.source.tree.ContinueTree; |
| import com.sun.source.tree.DoWhileLoopTree; |
| import com.sun.source.tree.EmptyStatementTree; |
| import com.sun.source.tree.EnhancedForLoopTree; |
| import com.sun.source.tree.ErroneousTree; |
| import com.sun.source.tree.ExpressionStatementTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.ForLoopTree; |
| import com.sun.source.tree.IdentifierTree; |
| import com.sun.source.tree.IfTree; |
| import com.sun.source.tree.ImportTree; |
| import com.sun.source.tree.InstanceOfTree; |
| import com.sun.source.tree.LabeledStatementTree; |
| import com.sun.source.tree.LambdaExpressionTree; |
| import com.sun.source.tree.LiteralTree; |
| import com.sun.source.tree.MemberReferenceTree; |
| import com.sun.source.tree.MemberSelectTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.ModifiersTree; |
| import com.sun.source.tree.NewArrayTree; |
| import com.sun.source.tree.NewClassTree; |
| import com.sun.source.tree.ParameterizedTypeTree; |
| import com.sun.source.tree.ParenthesizedTree; |
| import com.sun.source.tree.PrimitiveTypeTree; |
| import com.sun.source.tree.ReturnTree; |
| import com.sun.source.tree.StatementTree; |
| import com.sun.source.tree.SwitchTree; |
| import com.sun.source.tree.SynchronizedTree; |
| import com.sun.source.tree.ThrowTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.tree.TryTree; |
| import com.sun.source.tree.TypeCastTree; |
| import com.sun.source.tree.TypeParameterTree; |
| import com.sun.source.tree.UnaryTree; |
| import com.sun.source.tree.UnionTypeTree; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.tree.WhileLoopTree; |
| import com.sun.source.tree.WildcardTree; |
| import com.sun.source.util.TreePath; |
| import com.sun.source.util.TreePathScanner; |
| import com.sun.source.util.Trees; |
| import com.sun.tools.javac.code.Symbol.MethodSymbol; |
| import com.sun.tools.javac.code.Type; |
| import com.sun.tools.javac.processing.JavacProcessingEnvironment; |
| import com.sun.tools.javac.util.Context; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.Name; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.ArrayType; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.PrimitiveType; |
| import javax.lang.model.type.ReferenceType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.Elements; |
| import javax.lang.model.util.Types; |
| import org.checkerframework.checker.interning.qual.FindDistinct; |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.checkerframework.dataflow.analysis.Store.FlowRule; |
| import org.checkerframework.dataflow.cfg.UnderlyingAST; |
| import org.checkerframework.dataflow.cfg.node.ArrayAccessNode; |
| import org.checkerframework.dataflow.cfg.node.ArrayCreationNode; |
| import org.checkerframework.dataflow.cfg.node.ArrayTypeNode; |
| import org.checkerframework.dataflow.cfg.node.AssertionErrorNode; |
| import org.checkerframework.dataflow.cfg.node.AssignmentNode; |
| import org.checkerframework.dataflow.cfg.node.BitwiseAndNode; |
| import org.checkerframework.dataflow.cfg.node.BitwiseComplementNode; |
| import org.checkerframework.dataflow.cfg.node.BitwiseOrNode; |
| import org.checkerframework.dataflow.cfg.node.BitwiseXorNode; |
| import org.checkerframework.dataflow.cfg.node.BooleanLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.CaseNode; |
| import org.checkerframework.dataflow.cfg.node.CharacterLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.ClassDeclarationNode; |
| import org.checkerframework.dataflow.cfg.node.ClassNameNode; |
| import org.checkerframework.dataflow.cfg.node.ConditionalAndNode; |
| import org.checkerframework.dataflow.cfg.node.ConditionalNotNode; |
| import org.checkerframework.dataflow.cfg.node.ConditionalOrNode; |
| import org.checkerframework.dataflow.cfg.node.DoubleLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.EqualToNode; |
| import org.checkerframework.dataflow.cfg.node.ExplicitThisNode; |
| import org.checkerframework.dataflow.cfg.node.FieldAccessNode; |
| import org.checkerframework.dataflow.cfg.node.FloatLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.FloatingDivisionNode; |
| import org.checkerframework.dataflow.cfg.node.FloatingRemainderNode; |
| import org.checkerframework.dataflow.cfg.node.FunctionalInterfaceNode; |
| import org.checkerframework.dataflow.cfg.node.GreaterThanNode; |
| import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode; |
| import org.checkerframework.dataflow.cfg.node.ImplicitThisNode; |
| import org.checkerframework.dataflow.cfg.node.InstanceOfNode; |
| import org.checkerframework.dataflow.cfg.node.IntegerDivisionNode; |
| import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.IntegerRemainderNode; |
| import org.checkerframework.dataflow.cfg.node.LambdaResultExpressionNode; |
| import org.checkerframework.dataflow.cfg.node.LeftShiftNode; |
| import org.checkerframework.dataflow.cfg.node.LessThanNode; |
| import org.checkerframework.dataflow.cfg.node.LessThanOrEqualNode; |
| import org.checkerframework.dataflow.cfg.node.LocalVariableNode; |
| import org.checkerframework.dataflow.cfg.node.LongLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.MarkerNode; |
| import org.checkerframework.dataflow.cfg.node.MethodAccessNode; |
| import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; |
| import org.checkerframework.dataflow.cfg.node.NarrowingConversionNode; |
| import org.checkerframework.dataflow.cfg.node.Node; |
| import org.checkerframework.dataflow.cfg.node.NotEqualNode; |
| import org.checkerframework.dataflow.cfg.node.NullChkNode; |
| import org.checkerframework.dataflow.cfg.node.NullLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode; |
| import org.checkerframework.dataflow.cfg.node.NumericalMinusNode; |
| import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode; |
| import org.checkerframework.dataflow.cfg.node.NumericalPlusNode; |
| import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode; |
| import org.checkerframework.dataflow.cfg.node.ObjectCreationNode; |
| import org.checkerframework.dataflow.cfg.node.PackageNameNode; |
| import org.checkerframework.dataflow.cfg.node.ParameterizedTypeNode; |
| import org.checkerframework.dataflow.cfg.node.PrimitiveTypeNode; |
| import org.checkerframework.dataflow.cfg.node.ReturnNode; |
| import org.checkerframework.dataflow.cfg.node.SignedRightShiftNode; |
| import org.checkerframework.dataflow.cfg.node.StringConcatenateAssignmentNode; |
| import org.checkerframework.dataflow.cfg.node.StringConcatenateNode; |
| import org.checkerframework.dataflow.cfg.node.StringConversionNode; |
| import org.checkerframework.dataflow.cfg.node.StringLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.SuperNode; |
| import org.checkerframework.dataflow.cfg.node.SynchronizedNode; |
| import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode; |
| import org.checkerframework.dataflow.cfg.node.ThisNode; |
| import org.checkerframework.dataflow.cfg.node.ThrowNode; |
| import org.checkerframework.dataflow.cfg.node.TypeCastNode; |
| import org.checkerframework.dataflow.cfg.node.UnsignedRightShiftNode; |
| import org.checkerframework.dataflow.cfg.node.ValueLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.VariableDeclarationNode; |
| import org.checkerframework.dataflow.cfg.node.WideningConversionNode; |
| import org.checkerframework.dataflow.qual.TerminatesExecution; |
| import org.checkerframework.dataflow.util.IdentityMostlySingleton; |
| import org.checkerframework.javacutil.AnnotationProvider; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.Pair; |
| import org.checkerframework.javacutil.TreePathUtil; |
| import org.checkerframework.javacutil.TreeUtils; |
| import org.checkerframework.javacutil.TypeKindUtils; |
| import org.checkerframework.javacutil.TypesUtils; |
| import org.checkerframework.javacutil.trees.TreeBuilder; |
| import org.plumelib.util.CollectionsPlume; |
| |
| /** |
| * Class that performs phase one of the translation process. It generates the following information: |
| * |
| * <ul> |
| * <li>A sequence of extended nodes. |
| * <li>A set of bindings from {@link Label}s to positions in the node sequence. |
| * <li>A set of leader nodes that give rise to basic blocks in phase two. |
| * <li>A lookup map that gives the mapping from AST tree nodes to {@link Node}s. |
| * </ul> |
| * |
| * <p>The return type of this scanner is {@link Node}. For expressions, the corresponding node is |
| * returned to allow linking between different nodes. |
| * |
| * <p>However, for statements there is usually no single {@link Node} that is created, and thus no |
| * node is returned (rather, null is returned). |
| * |
| * <p>Every {@code visit*} method is assumed to add at least one extended node to the list of nodes |
| * (which might only be a jump). |
| */ |
| @SuppressWarnings("nullness") // TODO |
| public class CFGTranslationPhaseOne extends TreePathScanner<Node, Void> { |
| |
| /** Annotation processing environment and its associated type and tree utilities. */ |
| final ProcessingEnvironment env; |
| |
| final Elements elements; |
| final Types types; |
| final Trees trees; |
| public final TreeBuilder treeBuilder; |
| final AnnotationProvider annotationProvider; |
| |
| /** Can assertions be assumed to be disabled? */ |
| final boolean assumeAssertionsDisabled; |
| |
| /** Can assertions be assumed to be enabled? */ |
| final boolean assumeAssertionsEnabled; |
| |
| /* --------------------------------------------------------- */ |
| /* Extended Node Types and Labels */ |
| /* --------------------------------------------------------- */ |
| |
| /** Special label to identify the regular exit. */ |
| final Label regularExitLabel; |
| |
| /** Special label to identify the exceptional exit. */ |
| final Label exceptionalExitLabel; |
| |
| /** |
| * Current {@link TryFinallyScopeCell} to which a return statement should jump, or null if there |
| * is no valid destination. |
| */ |
| @Nullable TryFinallyScopeCell returnTargetL; |
| |
| /** |
| * Current {@link TryFinallyScopeCell} to which a break statement with no label should jump, or |
| * null if there is no valid destination. |
| */ |
| @Nullable TryFinallyScopeCell 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. |
| */ |
| Map<Name, Label> breakLabels; |
| |
| /** |
| * Current {@link TryFinallyScopeCell} to which a continue statement with no label should jump, or |
| * null if there is no valid destination. |
| */ |
| @Nullable TryFinallyScopeCell 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. |
| */ |
| Map<Name, Label> continueLabels; |
| |
| /** Nested scopes of try-catch blocks in force at the current program point. */ |
| private final TryStack tryStack; |
| |
| /** |
| * Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will |
| * have at least one corresponding Node. Trees that undergo conversions, such as boxing or |
| * unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in the |
| * treeLookupMap, while the Node for the post-conversion value is stored in the |
| * convertedTreeLookupMap. |
| */ |
| final IdentityHashMap<Tree, Set<Node>> treeLookupMap; |
| |
| /** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */ |
| final IdentityHashMap<Tree, Set<Node>> convertedTreeLookupMap; |
| |
| /** Map from AST {@link UnaryTree}s to compound {@link AssignmentNode}s. */ |
| final IdentityHashMap<UnaryTree, AssignmentNode> unaryAssignNodeLookupMap; |
| |
| /** The list of extended nodes. */ |
| final ArrayList<ExtendedNode> nodeList; |
| |
| /** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */ |
| final Map<Label, Integer> bindings; |
| |
| /** The set of leaders (represented as indices into {@code nodeList}). */ |
| final Set<Integer> leaders; |
| |
| /** |
| * All return nodes (if any) encountered. Only includes return statements that actually return |
| * something |
| */ |
| private final List<ReturnNode> returnNodes; |
| |
| /** |
| * Class declarations that have been encountered when building the control-flow graph for a |
| * method. |
| */ |
| final List<ClassTree> declaredClasses; |
| |
| /** |
| * Lambdas encountered when building the control-flow graph for a method, variable initializer, or |
| * initializer. |
| */ |
| final List<LambdaExpressionTree> declaredLambdas; |
| |
| /** The ArithmeticException type. */ |
| final TypeMirror arithmeticExceptionType; |
| |
| /** The ArrayIndexOutOfBoundsException type. */ |
| final TypeMirror arrayIndexOutOfBoundsExceptionType; |
| |
| /** The AssertionError type. */ |
| final TypeMirror assertionErrorType; |
| |
| /** The ClassCastException type . */ |
| final TypeMirror classCastExceptionType; |
| |
| /** The Iterable type (erased). */ |
| final TypeMirror iterableType; |
| |
| /** The NegativeArraySizeException type. */ |
| final TypeMirror negativeArraySizeExceptionType; |
| |
| /** The NullPointerException type . */ |
| final TypeMirror nullPointerExceptionType; |
| |
| /** The OutOfMemoryError type. */ |
| final @Nullable TypeMirror outOfMemoryErrorType; |
| |
| /** The ClassCircularityError type. */ |
| final @Nullable TypeMirror classCircularityErrorType; |
| |
| /** The ClassFormatErrorType type. */ |
| final @Nullable TypeMirror classFormatErrorType; |
| |
| /** The NoClassDefFoundError type. */ |
| final @Nullable TypeMirror noClassDefFoundErrorType; |
| |
| /** The String type. */ |
| final TypeMirror stringType; |
| |
| /** The Throwable type. */ |
| final TypeMirror throwableType; |
| |
| /** |
| * Supertypes of all unchecked exceptions. The size is 2 and the contents are {@code |
| * RuntimeException} and {@code Error}. |
| */ |
| final Set<TypeMirror> uncheckedExceptionTypes; |
| |
| /** |
| * Exceptions that can be thrown by array creation "new SomeType[]". The size is 2 and the |
| * contents are {@code NegativeArraySizeException} and {@code OutOfMemoryError}. This list comes |
| * from JLS 15.10.1 "Run-Time Evaluation of Array Creation Expressions". |
| */ |
| final Set<TypeMirror> newArrayExceptionTypes; |
| |
| /** |
| * @param treeBuilder builder for new AST nodes |
| * @param annotationProvider extracts annotations from AST nodes |
| * @param assumeAssertionsDisabled can assertions be assumed to be disabled? |
| * @param assumeAssertionsEnabled can assertions be assumed to be enabled? |
| * @param env annotation processing environment containing type utilities |
| */ |
| public CFGTranslationPhaseOne( |
| TreeBuilder treeBuilder, |
| AnnotationProvider annotationProvider, |
| boolean assumeAssertionsEnabled, |
| boolean assumeAssertionsDisabled, |
| ProcessingEnvironment env) { |
| this.env = env; |
| this.treeBuilder = treeBuilder; |
| this.annotationProvider = annotationProvider; |
| |
| assert !(assumeAssertionsDisabled && assumeAssertionsEnabled); |
| this.assumeAssertionsEnabled = assumeAssertionsEnabled; |
| this.assumeAssertionsDisabled = assumeAssertionsDisabled; |
| |
| elements = env.getElementUtils(); |
| types = env.getTypeUtils(); |
| trees = Trees.instance(env); |
| |
| // initialize lists and maps |
| treeLookupMap = new IdentityHashMap<>(); |
| convertedTreeLookupMap = new IdentityHashMap<>(); |
| unaryAssignNodeLookupMap = new IdentityHashMap<>(); |
| nodeList = new ArrayList<>(); |
| bindings = new HashMap<>(); |
| leaders = new HashSet<>(); |
| |
| regularExitLabel = new Label(); |
| exceptionalExitLabel = new Label(); |
| tryStack = new TryStack(exceptionalExitLabel); |
| returnTargetL = new TryFinallyScopeCell(regularExitLabel); |
| breakLabels = new HashMap<>(2); |
| continueLabels = new HashMap<>(2); |
| returnNodes = new ArrayList<>(); |
| declaredClasses = new ArrayList<>(); |
| declaredLambdas = new ArrayList<>(); |
| |
| arithmeticExceptionType = getTypeMirror(ArithmeticException.class); |
| arrayIndexOutOfBoundsExceptionType = getTypeMirror(ArrayIndexOutOfBoundsException.class); |
| assertionErrorType = getTypeMirror(AssertionError.class); |
| classCastExceptionType = getTypeMirror(ClassCastException.class); |
| iterableType = types.erasure(getTypeMirror(Iterable.class)); |
| negativeArraySizeExceptionType = getTypeMirror(NegativeArraySizeException.class); |
| nullPointerExceptionType = getTypeMirror(NullPointerException.class); |
| outOfMemoryErrorType = maybeGetTypeMirror(OutOfMemoryError.class); |
| classCircularityErrorType = maybeGetTypeMirror(ClassCircularityError.class); |
| classFormatErrorType = maybeGetTypeMirror(ClassFormatError.class); |
| noClassDefFoundErrorType = maybeGetTypeMirror(NoClassDefFoundError.class); |
| stringType = getTypeMirror(String.class); |
| throwableType = getTypeMirror(Throwable.class); |
| uncheckedExceptionTypes = new LinkedHashSet<>(2); |
| uncheckedExceptionTypes.add(getTypeMirror(RuntimeException.class)); |
| uncheckedExceptionTypes.add(getTypeMirror(Error.class)); |
| newArrayExceptionTypes = new LinkedHashSet<>(2); |
| newArrayExceptionTypes.add(negativeArraySizeExceptionType); |
| if (outOfMemoryErrorType != null) { |
| newArrayExceptionTypes.add(outOfMemoryErrorType); |
| } |
| } |
| |
| /** |
| * Performs the actual work of phase one. |
| * |
| * @param bodyPath path to the body of the underlying AST's method |
| * @param underlyingAST the AST for which the CFG is to be built |
| * @return the result of phase one |
| */ |
| public PhaseOneResult process(TreePath bodyPath, UnderlyingAST underlyingAST) { |
| // traverse AST of the method body |
| Node finalNode = scan(bodyPath, null); |
| |
| // If we are building the CFG for a lambda with a single expression as the body, then |
| // add an extra node for the result of that lambda |
| if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) { |
| LambdaExpressionTree lambdaTree = ((UnderlyingAST.CFGLambda) underlyingAST).getLambdaTree(); |
| if (lambdaTree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) { |
| Node resultNode = |
| new LambdaResultExpressionNode( |
| (ExpressionTree) lambdaTree.getBody(), finalNode, env.getTypeUtils()); |
| extendWithNode(resultNode); |
| } |
| } |
| |
| // Add marker to indicate that the next block will be the exit block. |
| // Note: if there is a return statement earlier in the method (which is always the case for |
| // non-void methods), then this is not strictly necessary. However, it is also not a problem, as |
| // it will just generate a degenerated control graph case that will be removed in a later phase. |
| nodeList.add(new UnconditionalJump(regularExitLabel)); |
| |
| return new PhaseOneResult( |
| underlyingAST, |
| treeLookupMap, |
| convertedTreeLookupMap, |
| unaryAssignNodeLookupMap, |
| nodeList, |
| bindings, |
| leaders, |
| returnNodes, |
| regularExitLabel, |
| exceptionalExitLabel, |
| declaredClasses, |
| declaredLambdas); |
| } |
| |
| public PhaseOneResult process(CompilationUnitTree root, UnderlyingAST underlyingAST) { |
| // TODO: Isn't this costly? Is there no cache we can reuse? |
| TreePath bodyPath = trees.getPath(root, underlyingAST.getCode()); |
| assert bodyPath != null; |
| return process(bodyPath, underlyingAST); |
| } |
| |
| /** |
| * Perform any actions required when CFG translation creates a new Tree that is not part of the |
| * original AST. |
| * |
| * @param tree the newly created Tree |
| */ |
| public void handleArtificialTree(Tree tree) {} |
| |
| /* --------------------------------------------------------- */ |
| /* Nodes and Labels Management */ |
| /* --------------------------------------------------------- */ |
| |
| /** |
| * Add a node to the lookup map if it not already present. |
| * |
| * @param node the node to add to the lookup map |
| */ |
| protected void addToLookupMap(Node node) { |
| Tree tree = node.getTree(); |
| if (tree == null) { |
| return; |
| } |
| Set<Node> existing = treeLookupMap.get(tree); |
| if (existing == null) { |
| treeLookupMap.put(tree, new IdentityMostlySingleton<>(node)); |
| } else { |
| existing.add(node); |
| } |
| |
| Tree enclosingParens = parenMapping.get(tree); |
| while (enclosingParens != null) { |
| Set<Node> exp = treeLookupMap.get(enclosingParens); |
| if (exp == null) { |
| treeLookupMap.put(enclosingParens, new IdentityMostlySingleton<>(node)); |
| } else if (!existing.contains(node)) { |
| exp.add(node); |
| } |
| enclosingParens = parenMapping.get(enclosingParens); |
| } |
| } |
| |
| /** |
| * Add a node in the post-conversion lookup map. The node should refer to a Tree and that Tree |
| * should already be in the pre-conversion lookup map. This method is used to update the Tree-Node |
| * mapping with conversion nodes. |
| * |
| * @param node the node to add to the lookup map |
| */ |
| protected void addToConvertedLookupMap(Node node) { |
| Tree tree = node.getTree(); |
| addToConvertedLookupMap(tree, node); |
| } |
| |
| /** |
| * Add a node in the post-conversion lookup map. The tree argument should already be in the |
| * pre-conversion lookup map. This method is used to update the Tree-Node mapping with conversion |
| * nodes. |
| * |
| * @param tree the tree used as a key in the map |
| * @param node the node to add to the lookup map |
| */ |
| protected void addToConvertedLookupMap(Tree tree, Node node) { |
| assert tree != null; |
| assert treeLookupMap.containsKey(tree); |
| Set<Node> existing = convertedTreeLookupMap.get(tree); |
| if (existing == null) { |
| convertedTreeLookupMap.put(tree, new IdentityMostlySingleton<>(node)); |
| } else { |
| existing.add(node); |
| } |
| } |
| |
| /** |
| * Add a unary tree in the compound assign lookup map. This method is used to update the |
| * UnaryTree-AssignmentNode mapping with compound assign nodes. |
| * |
| * @param tree the tree used as a key in the map |
| * @param unaryAssignNode the node to add to the lookup map |
| */ |
| protected void addToUnaryAssignLookupMap(UnaryTree tree, AssignmentNode unaryAssignNode) { |
| unaryAssignNodeLookupMap.put(tree, unaryAssignNode); |
| } |
| |
| /** |
| * Extend the list of extended nodes with a node. |
| * |
| * @param node the node to add |
| */ |
| protected void extendWithNode(Node node) { |
| addToLookupMap(node); |
| extendWithExtendedNode(new NodeHolder(node)); |
| } |
| |
| /** |
| * Extend the list of extended nodes with a node, where {@code node} might throw the exception |
| * {@code cause}. |
| * |
| * @param node the node to add |
| * @param cause an exception that the node might throw |
| * @return the node holder |
| */ |
| protected NodeWithExceptionsHolder extendWithNodeWithException(Node node, TypeMirror cause) { |
| addToLookupMap(node); |
| return extendWithNodeWithExceptions(node, Collections.singleton(cause)); |
| } |
| |
| /** |
| * Extend the list of extended nodes with a node, where {@code node} might throw any of the |
| * exceptions in {@code causes}. |
| * |
| * @param node the node to add |
| * @param causes set of exceptions that the node might throw |
| * @return the node holder |
| */ |
| protected NodeWithExceptionsHolder extendWithNodeWithExceptions( |
| Node node, Set<TypeMirror> causes) { |
| addToLookupMap(node); |
| Map<TypeMirror, Set<Label>> exceptions = new LinkedHashMap<>(causes.size()); |
| for (TypeMirror cause : causes) { |
| exceptions.put(cause, tryStack.possibleLabels(cause)); |
| } |
| NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); |
| extendWithExtendedNode(exNode); |
| return exNode; |
| } |
| |
| /** |
| * Extend a list of extended nodes with a ClassName node. |
| * |
| * <p>Evaluating a class literal kicks off class loading (JLS 15.8.2) which can fail and throw one |
| * of the specified subclasses of a LinkageError or an OutOfMemoryError (JLS 12.2.1). |
| * |
| * @param node the ClassName node to add |
| * @return the node holder |
| */ |
| protected NodeWithExceptionsHolder extendWithClassNameNode(ClassNameNode node) { |
| Set<TypeMirror> thrownSet = new LinkedHashSet<>(4); |
| if (classCircularityErrorType != null) { |
| thrownSet.add(classCircularityErrorType); |
| } |
| if (classFormatErrorType != null) { |
| thrownSet.add(classFormatErrorType); |
| } |
| if (noClassDefFoundErrorType != null) { |
| thrownSet.add(noClassDefFoundErrorType); |
| } |
| if (outOfMemoryErrorType != null) { |
| thrownSet.add(outOfMemoryErrorType); |
| } |
| |
| return extendWithNodeWithExceptions(node, thrownSet); |
| } |
| |
| /** |
| * Insert {@code node} after {@code pred} in the list of extended nodes, or append to the list if |
| * {@code pred} is not present. |
| * |
| * @param node the node to add |
| * @param pred the desired predecessor of node |
| * @return the node holder |
| */ |
| protected <T extends Node> T insertNodeAfter(T node, Node pred) { |
| addToLookupMap(node); |
| insertExtendedNodeAfter(new NodeHolder(node), pred); |
| return node; |
| } |
| |
| /** |
| * Insert a {@code node} that might throw the exceptions in {@code causes} after {@code pred} in |
| * the list of extended nodes, or append to the list if {@code pred} is not present. |
| * |
| * @param node the node to add |
| * @param causes set of exceptions that the node might throw |
| * @param pred the desired predecessor of node |
| * @return the node holder |
| */ |
| protected NodeWithExceptionsHolder insertNodeWithExceptionsAfter( |
| Node node, Set<TypeMirror> causes, Node pred) { |
| addToLookupMap(node); |
| Map<TypeMirror, Set<Label>> exceptions = new LinkedHashMap<>(causes.size()); |
| for (TypeMirror cause : causes) { |
| exceptions.put(cause, tryStack.possibleLabels(cause)); |
| } |
| NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions); |
| insertExtendedNodeAfter(exNode, pred); |
| return exNode; |
| } |
| |
| /** |
| * Extend the list of extended nodes with an extended node. |
| * |
| * @param n the extended node |
| */ |
| protected void extendWithExtendedNode(ExtendedNode n) { |
| nodeList.add(n); |
| } |
| |
| /** |
| * Insert {@code n} after the node {@code pred} in the list of extended nodes, or append {@code n} |
| * if {@code pred} is not present. |
| * |
| * @param n the extended node |
| * @param pred the desired predecessor |
| */ |
| @SuppressWarnings("ModifyCollectionInEnhancedForLoop") |
| protected void insertExtendedNodeAfter(ExtendedNode n, @FindDistinct Node pred) { |
| int index = -1; |
| for (int i = 0; i < nodeList.size(); i++) { |
| ExtendedNode inList = nodeList.get(i); |
| if (inList instanceof NodeHolder || inList instanceof NodeWithExceptionsHolder) { |
| if (inList.getNode() == pred) { |
| index = i; |
| break; |
| } |
| } |
| } |
| if (index != -1) { |
| nodeList.add(index + 1, n); |
| // update bindings |
| for (Map.Entry<Label, Integer> e : bindings.entrySet()) { |
| if (e.getValue() >= index + 1) { |
| bindings.put(e.getKey(), e.getValue() + 1); |
| } |
| } |
| // update leaders |
| Set<Integer> oldLeaders = new HashSet<>(leaders); |
| leaders.clear(); |
| for (Integer l : oldLeaders) { |
| if (l >= index + 1) { |
| leaders.add(l + 1); |
| } else { |
| leaders.add(l); |
| } |
| } |
| } else { |
| nodeList.add(n); |
| } |
| } |
| |
| /** Add the label {@code l} to the extended node that will be placed next in the sequence. */ |
| protected void addLabelForNextNode(Label l) { |
| assert !bindings.containsKey(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); |
| // No need to handle possible errors from evaluating a class literal here |
| // since this is synthetic code that can't fail. |
| ClassNameNode className = new ClassNameNode(classTree); |
| className.setInSource(false); |
| insertNodeAfter(className, node); |
| |
| MemberSelectTree valueOfSelect = treeBuilder.buildValueOfMethodAccess(classTree); |
| handleArtificialTree(valueOfSelect); |
| MethodAccessNode valueOfAccess = new MethodAccessNode(valueOfSelect, className); |
| valueOfAccess.setInSource(false); |
| insertNodeAfter(valueOfAccess, className); |
| |
| MethodInvocationTree valueOfCall = |
| treeBuilder.buildMethodInvocation(valueOfSelect, (ExpressionTree) node.getTree()); |
| handleArtificialTree(valueOfCall); |
| Node boxed = |
| new MethodInvocationNode( |
| valueOfCall, valueOfAccess, Collections.singletonList(node), getCurrentPath()); |
| boxed.setInSource(false); |
| // Add Throwable to account for unchecked exceptions |
| addToConvertedLookupMap(node.getTree(), boxed); |
| insertNodeWithExceptionsAfter(boxed, uncheckedExceptionTypes, valueOfAccess); |
| return boxed; |
| } else { |
| return node; |
| } |
| } |
| |
| /** |
| * If the input node is a boxed type, unbox it, otherwise leave it alone. |
| * |
| * @param node in input node |
| * @return a Node representing the unboxed version of the input, which may simply be the input |
| * node |
| */ |
| protected Node unbox(Node node) { |
| if (TypesUtils.isBoxedPrimitive(node.getType())) { |
| |
| MemberSelectTree primValueSelect = treeBuilder.buildPrimValueMethodAccess(node.getTree()); |
| handleArtificialTree(primValueSelect); |
| MethodAccessNode primValueAccess = new MethodAccessNode(primValueSelect, node); |
| primValueAccess.setInSource(false); |
| // Method access may throw NullPointerException |
| insertNodeWithExceptionsAfter( |
| primValueAccess, Collections.singleton(nullPointerExceptionType), node); |
| |
| MethodInvocationTree primValueCall = treeBuilder.buildMethodInvocation(primValueSelect); |
| handleArtificialTree(primValueCall); |
| Node unboxed = |
| new MethodInvocationNode( |
| primValueCall, primValueAccess, Collections.emptyList(), getCurrentPath()); |
| unboxed.setInSource(false); |
| |
| // Add Throwable to account for unchecked exceptions |
| addToConvertedLookupMap(node.getTree(), unboxed); |
| insertNodeWithExceptionsAfter(unboxed, uncheckedExceptionTypes, primValueAccess); |
| return unboxed; |
| } else { |
| return node; |
| } |
| } |
| |
| private TreeInfo getTreeInfo(Tree tree) { |
| final TypeMirror type = TreeUtils.typeOf(tree); |
| final boolean boxed = TypesUtils.isBoxedPrimitive(type); |
| final TypeMirror unboxedType = boxed ? types.unboxedType(type) : type; |
| |
| final boolean bool = TypesUtils.isBooleanType(type); |
| final boolean numeric = TypesUtils.isNumeric(unboxedType); |
| |
| return new TreeInfo() { |
| @Override |
| public boolean isNumeric() { |
| return numeric; |
| } |
| |
| @Override |
| public boolean isBoxed() { |
| return boxed; |
| } |
| |
| @Override |
| public boolean isBoolean() { |
| return bool; |
| } |
| |
| @Override |
| public TypeMirror unboxedType() { |
| return unboxedType; |
| } |
| }; |
| } |
| |
| /** |
| * Returns the unboxed tree if necessary, as described in JLS 5.1.8. |
| * |
| * @return the unboxed tree if necessary, as described in JLS 5.1.8 |
| */ |
| private Node unboxAsNeeded(Node node, boolean boxed) { |
| return boxed ? unbox(node) : node; |
| } |
| |
| /** |
| * Convert the input node to String type, if it isn't already. |
| * |
| * @param node an input node |
| * @return a Node with the value promoted to String, which may be the input node |
| */ |
| protected Node stringConversion(Node node) { |
| // For string conversion, see JLS 5.1.11 |
| if (!TypesUtils.isString(node.getType())) { |
| Node converted = new StringConversionNode(node.getTree(), node, stringType); |
| addToConvertedLookupMap(converted); |
| insertNodeAfter(converted, node); |
| return converted; |
| } else { |
| return node; |
| } |
| } |
| |
| /** |
| * Perform unary numeric promotion on the input node. |
| * |
| * @param node a node producing a value of numeric primitive or boxed type |
| * @return a Node with the value promoted to the int, long, float, or double; may return be the |
| * input node |
| */ |
| protected Node unaryNumericPromotion(Node node) { |
| // For unary numeric promotion, see JLS 5.6.1 |
| node = unbox(node); |
| |
| switch (node.getType().getKind()) { |
| case BYTE: |
| case CHAR: |
| case SHORT: |
| { |
| TypeMirror intType = types.getPrimitiveType(TypeKind.INT); |
| Node widened = new WideningConversionNode(node.getTree(), node, intType); |
| addToConvertedLookupMap(widened); |
| insertNodeAfter(widened, node); |
| return widened; |
| } |
| default: |
| // Nothing to do. |
| break; |
| } |
| |
| return node; |
| } |
| |
| /** |
| * Returns true if the argument type is a numeric primitive or a boxed numeric primitive and false |
| * otherwise. |
| */ |
| protected boolean isNumericOrBoxed(TypeMirror type) { |
| if (TypesUtils.isBoxedPrimitive(type)) { |
| type = types.unboxedType(type); |
| } |
| return TypesUtils.isNumeric(type); |
| } |
| |
| /** |
| * Compute the type to which two numeric types must be promoted before performing a binary numeric |
| * operation on them. The input types must both be numeric and the output type is primitive. |
| * |
| * @param left the type of the left operand |
| * @param right the type of the right operand |
| * @return a TypeMirror representing the binary numeric promoted type |
| */ |
| protected TypeMirror binaryPromotedType(TypeMirror left, TypeMirror right) { |
| if (TypesUtils.isBoxedPrimitive(left)) { |
| left = types.unboxedType(left); |
| } |
| if (TypesUtils.isBoxedPrimitive(right)) { |
| right = types.unboxedType(right); |
| } |
| TypeKind promotedTypeKind = TypeKindUtils.widenedNumericType(left, right); |
| return types.getPrimitiveType(promotedTypeKind); |
| } |
| |
| /** |
| * Perform binary numeric promotion on the input node to make it match the expression type. |
| * |
| * @param node a node producing a value of numeric primitive or boxed type |
| * @param exprType the type to promote the value to |
| * @return a Node with the value promoted to the exprType, which may be the input node |
| */ |
| protected Node binaryNumericPromotion(Node node, TypeMirror exprType) { |
| // For binary numeric promotion, see JLS 5.6.2 |
| node = unbox(node); |
| |
| if (!types.isSameType(node.getType(), exprType)) { |
| Node widened = new WideningConversionNode(node.getTree(), node, exprType); |
| addToConvertedLookupMap(widened); |
| insertNodeAfter(widened, node); |
| return widened; |
| } else { |
| return node; |
| } |
| } |
| |
| /** |
| * Perform widening primitive conversion on the input node to make it match the destination type. |
| * |
| * @param node a node producing a value of numeric primitive type |
| * @param destType the type to widen the value to |
| * @return a Node with the value widened to the exprType, which may be the input node |
| */ |
| protected Node widen(Node node, TypeMirror destType) { |
| // For widening conversion, see JLS 5.1.2 |
| assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) |
| : "widening must be applied to primitive types"; |
| if (types.isSubtype(node.getType(), destType) && !types.isSameType(node.getType(), destType)) { |
| Node widened = new WideningConversionNode(node.getTree(), node, destType); |
| addToConvertedLookupMap(widened); |
| insertNodeAfter(widened, node); |
| return widened; |
| } else { |
| return node; |
| } |
| } |
| |
| /** |
| * Perform narrowing conversion on the input node to make it match the destination type. |
| * |
| * @param node a node producing a value of numeric primitive type |
| * @param destType the type to narrow the value to |
| * @return a Node with the value narrowed to the exprType, which may be the input node |
| */ |
| protected Node narrow(Node node, TypeMirror destType) { |
| // For narrowing conversion, see JLS 5.1.3 |
| assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType) |
| : "narrowing must be applied to primitive types"; |
| if (types.isSubtype(destType, node.getType()) && !types.isSameType(destType, node.getType())) { |
| Node narrowed = new NarrowingConversionNode(node.getTree(), node, destType); |
| addToConvertedLookupMap(narrowed); |
| insertNodeAfter(narrowed, node); |
| return narrowed; |
| } else { |
| return node; |
| } |
| } |
| |
| /** |
| * Perform narrowing conversion and optionally boxing conversion on the input node to make it |
| * match the destination type. |
| * |
| * @param node a node producing a value of numeric primitive type |
| * @param destType the type to narrow the value to (possibly boxed) |
| * @return a Node with the value narrowed and boxed to the destType, which may be the input node |
| */ |
| protected Node narrowAndBox(Node node, TypeMirror destType) { |
| if (TypesUtils.isBoxedPrimitive(destType)) { |
| return box(narrow(node, types.unboxedType(destType))); |
| } else { |
| return narrow(node, destType); |
| } |
| } |
| |
| /** |
| * Return whether a conversion from the type of the node to varType requires narrowing. |
| * |
| * @param varType the type of a variable (or general LHS) to be converted to |
| * @param node a node whose value is being converted |
| * @return whether this conversion requires narrowing to succeed |
| */ |
| protected boolean conversionRequiresNarrowing(TypeMirror varType, Node node) { |
| // Narrowing is restricted to cases where the left hand side is byte, char, short or Byte, Char, |
| // Short and the right hand side is a constant. |
| TypeMirror unboxedVarType = |
| TypesUtils.isBoxedPrimitive(varType) ? types.unboxedType(varType) : varType; |
| TypeKind unboxedVarKind = unboxedVarType.getKind(); |
| boolean isLeftNarrowableTo = |
| unboxedVarKind == TypeKind.BYTE |
| || unboxedVarKind == TypeKind.SHORT |
| || unboxedVarKind == TypeKind.CHAR; |
| boolean isRightConstant = node instanceof ValueLiteralNode; |
| return isLeftNarrowableTo && isRightConstant; |
| } |
| |
| /** |
| * Assignment conversion and method invocation conversion are almost identical, except that |
| * assignment conversion allows narrowing. We factor out the common logic here. |
| * |
| * @param node a Node producing a value |
| * @param varType the type of a variable |
| * @param contextAllowsNarrowing whether to allow narrowing (for assignment conversion) or not |
| * (for method invocation conversion) |
| * @return a Node with the value converted to the type of the variable, which may be the input |
| * node itself |
| */ |
| protected Node commonConvert(Node node, TypeMirror varType, boolean contextAllowsNarrowing) { |
| // For assignment conversion, see JLS 5.2 |
| // For method invocation conversion, see JLS 5.3 |
| |
| // Check for identical types or "identity conversion" |
| TypeMirror nodeType = node.getType(); |
| boolean isSameType = types.isSameType(nodeType, varType); |
| if (isSameType) { |
| return node; |
| } |
| |
| boolean isRightNumeric = TypesUtils.isNumeric(nodeType); |
| boolean isRightPrimitive = TypesUtils.isPrimitive(nodeType); |
| boolean isRightBoxed = TypesUtils.isBoxedPrimitive(nodeType); |
| boolean isRightReference = nodeType instanceof ReferenceType; |
| boolean isLeftNumeric = TypesUtils.isNumeric(varType); |
| boolean isLeftPrimitive = TypesUtils.isPrimitive(varType); |
| // boolean isLeftBoxed = TypesUtils.isBoxedPrimitive(varType); |
| boolean isLeftReference = varType instanceof ReferenceType; |
| boolean isSubtype = types.isSubtype(nodeType, varType); |
| |
| if (isRightNumeric && isLeftNumeric && isSubtype) { |
| node = widen(node, varType); |
| 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<Node> convertCallArguments( |
| ExecutableElement method, List<? extends ExpressionTree> 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<? extends VariableElement> formals = method.getParameters(); |
| int numFormals = formals.size(); |
| |
| ArrayList<Node> convertedNodes = new ArrayList<>(numFormals); |
| |
| 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(); |
| if (numActuals == numFormals |
| && types.isAssignable(TreeUtils.typeOf(actualExprs.get(numActuals - 1)), lastParamType)) { |
| // Normal call with no array creation, apply method |
| // invocation conversion to all arguments. |
| for (int i = 0; i < numActuals; i++) { |
| Node actualVal = scan(actualExprs.get(i), null); |
| if (actualVal == null) { |
| throw new BugInCF( |
| "CFGBuilder: scan returned null for %s [%s]", |
| actualExprs.get(i), actualExprs.get(i).getClass()); |
| } |
| convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i).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(); |
| List<ExpressionTree> inits = new ArrayList<>(numActuals - lastArgIndex); |
| List<Node> initializers = new ArrayList<>(numActuals - lastArgIndex); |
| for (int i = lastArgIndex; i < numActuals; i++) { |
| inits.add(actualExprs.get(i)); |
| Node actualVal = scan(actualExprs.get(i), null); |
| initializers.add(assignConvert(actualVal, elemType)); |
| } |
| |
| NewArrayTree wrappedVarargs = treeBuilder.buildNewArray(elemType, inits); |
| handleArtificialTree(wrappedVarargs); |
| |
| Node lastArgument = |
| new ArrayCreationNode( |
| wrappedVarargs, |
| lastParamType, |
| /*dimensions=*/ Collections.emptyList(), |
| initializers); |
| extendWithNode(lastArgument); |
| |
| convertedNodes.add(lastArgument); |
| } |
| } else { |
| for (int i = 0; i < numActuals; i++) { |
| Node actualVal = scan(actualExprs.get(i), null); |
| 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) { |
| throw new Error("AnnotationTree is unexpected in AST to CFG translation"); |
| } |
| |
| @Override |
| public MethodInvocationNode visitMethodInvocation(MethodInvocationTree tree, Void p) { |
| |
| // see JLS 15.12.4 |
| |
| // First, compute the receiver, if any (15.12.4.1). |
| // Second, evaluate the actual arguments, left to right and possibly some arguments are stored |
| // into an array for variable arguments calls (15.12.4.2). |
| // Third, test the receiver, if any, for nullness (15.12.4.4). |
| // Fourth, convert the arguments to the type of the formal parameters (15.12.4.5). |
| // Fifth, if the method is synchronized, lock the receiving object or class (15.12.4.5). |
| ExecutableElement method = TreeUtils.elementFromUse(tree); |
| if (method == null) { |
| // The method wasn't found, e.g. because of a compilation error. |
| return null; |
| } |
| |
| ExpressionTree methodSelect = tree.getMethodSelect(); |
| assert TreeUtils.isMethodAccess(methodSelect) |
| : "Expected a method access, but got: " + methodSelect; |
| |
| List<? extends ExpressionTree> actualExprs = tree.getArguments(); |
| |
| // Look up method to invoke and possibly throw NullPointerException |
| Node receiver = getReceiver(methodSelect); |
| |
| MethodAccessNode target = new MethodAccessNode(methodSelect, receiver); |
| |
| ExecutableElement element = TreeUtils.elementFromUse(tree); |
| if (ElementUtils.isStatic(element) || receiver instanceof ThisNode) { |
| // No NullPointerException can be thrown, use normal node |
| extendWithNode(target); |
| } else { |
| extendWithNodeWithException(target, nullPointerExceptionType); |
| } |
| |
| List<Node> arguments; |
| if (TreeUtils.isEnumSuper(tree)) { |
| // Don't convert arguments for enum super calls. The AST contains no actual arguments, while |
| // the method element expects two arguments, leading to an exception in convertCallArguments. |
| // Since no actual arguments are present in the AST that is being checked, it shouldn't cause |
| // any harm to omit the conversions. |
| // See also BaseTypeVisitor.visitMethodInvocation and QualifierPolymorphism.annotate. |
| arguments = Collections.emptyList(); |
| } else { |
| arguments = convertCallArguments(method, actualExprs); |
| } |
| |
| // TODO: lock the receiver for synchronized methods |
| |
| MethodInvocationNode node = new MethodInvocationNode(tree, target, arguments, getCurrentPath()); |
| |
| List<? extends TypeMirror> thrownTypes = element.getThrownTypes(); |
| Set<TypeMirror> thrownSet = |
| new LinkedHashSet<>(thrownTypes.size() + uncheckedExceptionTypes.size()); |
| // Add exceptions explicitly mentioned in the throws clause. |
| thrownSet.addAll(thrownTypes); |
| // Add types to account for unchecked exceptions |
| thrownSet.addAll(uncheckedExceptionTypes); |
| |
| ExtendedNode extendedNode = extendWithNodeWithExceptions(node, thrownSet); |
| |
| /* Check for the TerminatesExecution annotation. */ |
| Element methodElement = TreeUtils.elementFromTree(tree); |
| boolean terminatesExecution = |
| annotationProvider.getDeclAnnotation(methodElement, TerminatesExecution.class) != null; |
| if (terminatesExecution) { |
| extendedNode.setTerminatesExecution(true); |
| } |
| |
| return node; |
| } |
| |
| @Override |
| public Node visitAssert(AssertTree tree, Void p) { |
| |
| // see JLS 14.10 |
| |
| // If assertions are enabled, then we can just translate the |
| // assertion. |
| if (assumeAssertionsEnabled || assumeAssertionsEnabledFor(tree)) { |
| translateAssertWithAssertionsEnabled(tree); |
| return null; |
| } |
| |
| // If assertions are disabled, then nothing is executed. |
| if (assumeAssertionsDisabled) { |
| return null; |
| } |
| |
| // Otherwise, we don't know if assertions are enabled, so we use a |
| // variable "ea" and case-split on it. One branch does execute the |
| // assertion, while the other assumes assertions are disabled. |
| VariableTree ea = getAssertionsEnabledVariable(); |
| |
| // all necessary labels |
| Label assertionEnabled = new Label(); |
| Label assertionDisabled = new Label(); |
| |
| extendWithNode(new LocalVariableNode(ea)); |
| extendWithExtendedNode(new ConditionalJump(assertionEnabled, assertionDisabled)); |
| |
| // 'then' branch (i.e. check the assertion) |
| addLabelForNextNode(assertionEnabled); |
| |
| translateAssertWithAssertionsEnabled(tree); |
| |
| // 'else' branch |
| addLabelForNextNode(assertionDisabled); |
| |
| return null; |
| } |
| |
| /** |
| * Should assertions be assumed to be executed for a given {@link AssertTree}? False by default. |
| */ |
| protected boolean assumeAssertionsEnabledFor(AssertTree tree) { |
| return false; |
| } |
| |
| /** The {@link VariableTree} that indicates whether assertions are enabled or not. */ |
| protected VariableTree ea = null; |
| |
| /** Get a synthetic {@link VariableTree} that indicates whether assertions are enabled or not. */ |
| protected VariableTree getAssertionsEnabledVariable() { |
| if (ea == null) { |
| String name = uniqueName("assertionsEnabled"); |
| Element owner = findOwner(); |
| ExpressionTree initializer = null; |
| ea = |
| treeBuilder.buildVariableDecl( |
| types.getPrimitiveType(TypeKind.BOOLEAN), name, owner, initializer); |
| } |
| return ea; |
| } |
| |
| /** |
| * Find nearest owner element(Method or Class) which holds current tree. |
| * |
| * @return nearest owner element of current tree |
| */ |
| private Element findOwner() { |
| MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath()); |
| if (enclosingMethod != null) { |
| return TreeUtils.elementFromDeclaration(enclosingMethod); |
| } else { |
| ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath()); |
| return TreeUtils.elementFromDeclaration(enclosingClass); |
| } |
| } |
| |
| /** |
| * Translates an assertion statement to the correct CFG nodes. The translation assumes that |
| * assertions are enabled. |
| */ |
| protected void translateAssertWithAssertionsEnabled(AssertTree tree) { |
| |
| // all necessary labels |
| Label assertEnd = new Label(); |
| Label elseEntry = new Label(); |
| |
| // basic block for the condition |
| Node condition = unbox(scan(tree.getCondition(), null)); |
| ConditionalJump cjump = new ConditionalJump(assertEnd, elseEntry); |
| extendWithExtendedNode(cjump); |
| |
| // else branch |
| Node detail = null; |
| addLabelForNextNode(elseEntry); |
| if (tree.getDetail() != null) { |
| detail = scan(tree.getDetail(), null); |
| } |
| AssertionErrorNode assertNode = |
| new AssertionErrorNode(tree, condition, detail, assertionErrorType); |
| extendWithNode(assertNode); |
| NodeWithExceptionsHolder exNode = |
| extendWithNodeWithException( |
| new ThrowNode(null, assertNode, env.getTypeUtils()), assertionErrorType); |
| exNode.setTerminatesExecution(true); |
| |
| // then branch (nothing happens) |
| addLabelForNextNode(assertEnd); |
| } |
| |
| @Override |
| public Node visitAssignment(AssignmentTree tree, Void p) { |
| |
| // see JLS 15.26.1 |
| |
| AssignmentNode assignmentNode; |
| ExpressionTree variable = tree.getVariable(); |
| TypeMirror varType = TreeUtils.typeOf(variable); |
| |
| // case 1: lhs is field access |
| if (TreeUtils.isFieldAccess(variable)) { |
| // visit receiver |
| Node receiver = getReceiver(variable); |
| |
| // visit expression |
| Node expression = scan(tree.getExpression(), p); |
| expression = assignConvert(expression, varType); |
| |
| // visit field access (throws null-pointer exception) |
| FieldAccessNode target = new FieldAccessNode(variable, receiver); |
| target.setLValue(); |
| |
| Element element = TreeUtils.elementFromUse(variable); |
| if (ElementUtils.isStatic(element) || receiver instanceof ThisNode) { |
| // No NullPointerException can be thrown, use normal node |
| extendWithNode(target); |
| } else { |
| extendWithNodeWithException(target, nullPointerExceptionType); |
| } |
| |
| // add assignment node |
| assignmentNode = new AssignmentNode(tree, target, expression); |
| extendWithNode(assignmentNode); |
| } |
| |
| // case 2: lhs is not a field access |
| else { |
| Node target = scan(variable, p); |
| target.setLValue(); |
| |
| assignmentNode = translateAssignment(tree, target, tree.getExpression()); |
| } |
| |
| return assignmentNode; |
| } |
| |
| /** Translate an assignment. */ |
| protected AssignmentNode translateAssignment(Tree tree, Node target, ExpressionTree rhs) { |
| Node expression = scan(rhs, null); |
| return translateAssignment(tree, target, expression); |
| } |
| |
| /** Translate an assignment where the RHS has already been scanned. */ |
| protected AssignmentNode translateAssignment(Tree tree, Node target, Node expression) { |
| assert tree instanceof AssignmentTree || tree instanceof VariableTree; |
| target.setLValue(); |
| expression = assignConvert(expression, target.getType()); |
| AssignmentNode assignmentNode = new AssignmentNode(tree, target, expression); |
| extendWithNode(assignmentNode); |
| return assignmentNode; |
| } |
| |
| /** |
| * Note 1: Requires {@code tree} to be a field or method access tree. |
| * |
| * <p>Note 2: Visits the receiver and adds all necessary blocks to the CFG. |
| * |
| * @param tree the field access tree containing the receiver |
| * @return the receiver of the field access |
| */ |
| private Node getReceiver(ExpressionTree tree) { |
| assert TreeUtils.isFieldAccess(tree) || TreeUtils.isMethodAccess(tree); |
| if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { |
| MemberSelectTree mtree = (MemberSelectTree) tree; |
| return scan(mtree.getExpression(), null); |
| } else { |
| Element ele = TreeUtils.elementFromUse(tree); |
| TypeElement declaringClass = ElementUtils.enclosingTypeElement(ele); |
| TypeMirror type = ElementUtils.getType(declaringClass); |
| if (ElementUtils.isStatic(ele)) { |
| ClassNameNode node = new ClassNameNode(type, declaringClass); |
| extendWithClassNameNode(node); |
| return node; |
| } else { |
| Node node = new ImplicitThisNode(type); |
| extendWithNode(node); |
| return node; |
| } |
| } |
| } |
| |
| /** |
| * Map an operation with assignment to the corresponding operation without assignment. |
| * |
| * @param kind a Tree.Kind representing an operation with assignment |
| * @return the Tree.Kind for the same operation without assignment |
| */ |
| protected Tree.Kind withoutAssignment(Tree.Kind kind) { |
| switch (kind) { |
| case DIVIDE_ASSIGNMENT: |
| return Tree.Kind.DIVIDE; |
| case MULTIPLY_ASSIGNMENT: |
| return Tree.Kind.MULTIPLY; |
| case REMAINDER_ASSIGNMENT: |
| return Tree.Kind.REMAINDER; |
| case MINUS_ASSIGNMENT: |
| return Tree.Kind.MINUS; |
| case PLUS_ASSIGNMENT: |
| return Tree.Kind.PLUS; |
| case LEFT_SHIFT_ASSIGNMENT: |
| return Tree.Kind.LEFT_SHIFT; |
| case RIGHT_SHIFT_ASSIGNMENT: |
| return Tree.Kind.RIGHT_SHIFT; |
| case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: |
| return Tree.Kind.UNSIGNED_RIGHT_SHIFT; |
| case AND_ASSIGNMENT: |
| return Tree.Kind.AND; |
| case OR_ASSIGNMENT: |
| return Tree.Kind.OR; |
| case XOR_ASSIGNMENT: |
| return Tree.Kind.XOR; |
| default: |
| return Tree.Kind.ERRONEOUS; |
| } |
| } |
| |
| @Override |
| public Node visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { |
| // According the JLS 15.26.2, E1 op= E2 is equivalent to |
| // E1 = (T) ((E1) op (E2)), where T is the type of E1, |
| // except that E1 is evaluated only once. |
| // |
| |
| Tree.Kind kind = tree.getKind(); |
| switch (kind) { |
| case DIVIDE_ASSIGNMENT: |
| case MULTIPLY_ASSIGNMENT: |
| case REMAINDER_ASSIGNMENT: |
| { |
| // see JLS 15.17 and 15.26.2 |
| Node targetLHS = scan(tree.getVariable(), p); |
| Node value = scan(tree.getExpression(), p); |
| |
| TypeMirror exprType = TreeUtils.typeOf(tree); |
| TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); |
| TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); |
| TypeMirror promotedType = binaryPromotedType(leftType, rightType); |
| Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); |
| value = binaryNumericPromotion(value, promotedType); |
| |
| BinaryTree operTree = |
| treeBuilder.buildBinary( |
| promotedType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); |
| handleArtificialTree(operTree); |
| Node operNode; |
| if (kind == Tree.Kind.MULTIPLY_ASSIGNMENT) { |
| operNode = new NumericalMultiplicationNode(operTree, targetRHS, value); |
| } else if (kind == Tree.Kind.DIVIDE_ASSIGNMENT) { |
| if (TypesUtils.isIntegralPrimitive(exprType)) { |
| operNode = new IntegerDivisionNode(operTree, targetRHS, value); |
| |
| extendWithNodeWithException(operNode, arithmeticExceptionType); |
| } else { |
| operNode = new FloatingDivisionNode(operTree, targetRHS, value); |
| } |
| } else { |
| assert kind == Kind.REMAINDER_ASSIGNMENT; |
| if (TypesUtils.isIntegralPrimitive(exprType)) { |
| operNode = new IntegerRemainderNode(operTree, targetRHS, value); |
| |
| extendWithNodeWithException(operNode, arithmeticExceptionType); |
| } else { |
| operNode = new FloatingRemainderNode(operTree, targetRHS, value); |
| } |
| } |
| extendWithNode(operNode); |
| |
| TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree); |
| handleArtificialTree(castTree); |
| TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType, types); |
| castNode.setInSource(false); |
| extendWithNode(castNode); |
| |
| AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); |
| extendWithNode(assignNode); |
| return assignNode; |
| } |
| |
| case MINUS_ASSIGNMENT: |
| case PLUS_ASSIGNMENT: |
| { |
| // see JLS 15.18 and 15.26.2 |
| |
| Node targetLHS = scan(tree.getVariable(), p); |
| Node value = scan(tree.getExpression(), p); |
| |
| TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); |
| TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); |
| |
| if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { |
| assert (kind == Tree.Kind.PLUS_ASSIGNMENT); |
| Node targetRHS = stringConversion(targetLHS); |
| value = stringConversion(value); |
| 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, types); |
| castNode.setInSource(false); |
| extendWithNode(castNode); |
| |
| // Map the compound assignment tree to an assignment node, which |
| // will have the correct type. |
| AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); |
| extendWithNode(assignNode); |
| return assignNode; |
| } |
| } |
| |
| case LEFT_SHIFT_ASSIGNMENT: |
| case RIGHT_SHIFT_ASSIGNMENT: |
| case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: |
| { |
| // see JLS 15.19 and 15.26.2 |
| Node targetLHS = scan(tree.getVariable(), p); |
| Node value = scan(tree.getExpression(), p); |
| |
| TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); |
| |
| Node targetRHS = unaryNumericPromotion(targetLHS); |
| value = unaryNumericPromotion(value); |
| |
| BinaryTree operTree = |
| treeBuilder.buildBinary( |
| leftType, withoutAssignment(kind), tree.getVariable(), tree.getExpression()); |
| handleArtificialTree(operTree); |
| Node operNode; |
| if (kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT) { |
| operNode = new LeftShiftNode(operTree, targetRHS, value); |
| } else if (kind == Tree.Kind.RIGHT_SHIFT_ASSIGNMENT) { |
| operNode = new SignedRightShiftNode(operTree, targetRHS, value); |
| } else { |
| assert kind == 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, types); |
| castNode.setInSource(false); |
| extendWithNode(castNode); |
| |
| AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); |
| extendWithNode(assignNode); |
| return assignNode; |
| } |
| |
| case AND_ASSIGNMENT: |
| case OR_ASSIGNMENT: |
| case XOR_ASSIGNMENT: |
| // see JLS 15.22 |
| Node targetLHS = scan(tree.getVariable(), p); |
| Node value = scan(tree.getExpression(), p); |
| |
| TypeMirror leftType = TreeUtils.typeOf(tree.getVariable()); |
| TypeMirror rightType = TreeUtils.typeOf(tree.getExpression()); |
| |
| Node targetRHS = null; |
| if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { |
| TypeMirror promotedType = binaryPromotedType(leftType, rightType); |
| targetRHS = binaryNumericPromotion(targetLHS, promotedType); |
| value = binaryNumericPromotion(value, promotedType); |
| } else if (TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType)) { |
| targetRHS = unbox(targetLHS); |
| value = unbox(value); |
| } else { |
| throw new Error("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, types); |
| castNode.setInSource(false); |
| extendWithNode(castNode); |
| |
| AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); |
| extendWithNode(assignNode); |
| return assignNode; |
| default: |
| throw new Error("unexpected compound assignment type"); |
| } |
| } |
| |
| @Override |
| public Node visitBinary(BinaryTree tree, Void p) { |
| // Note that for binary operations it is important to perform any required promotion on the left |
| // operand before generating any Nodes for the right operand, because labels must be inserted |
| // AFTER ALL preceding Nodes and BEFORE ALL following Nodes. |
| Node r = null; |
| Tree leftTree = tree.getLeftOperand(); |
| Tree rightTree = tree.getRightOperand(); |
| |
| Tree.Kind kind = tree.getKind(); |
| switch (kind) { |
| case DIVIDE: |
| case MULTIPLY: |
| case REMAINDER: |
| { |
| // see JLS 15.17 |
| |
| TypeMirror exprType = TreeUtils.typeOf(tree); |
| TypeMirror leftType = TreeUtils.typeOf(leftTree); |
| TypeMirror rightType = TreeUtils.typeOf(rightTree); |
| TypeMirror promotedType = binaryPromotedType(leftType, rightType); |
| |
| Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); |
| Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); |
| |
| if (kind == Tree.Kind.MULTIPLY) { |
| r = new NumericalMultiplicationNode(tree, left, right); |
| } else if (kind == Tree.Kind.DIVIDE) { |
| if (TypesUtils.isIntegralPrimitive(exprType)) { |
| r = new IntegerDivisionNode(tree, left, right); |
| |
| extendWithNodeWithException(r, arithmeticExceptionType); |
| } else { |
| r = new FloatingDivisionNode(tree, left, right); |
| } |
| } else { |
| assert kind == Kind.REMAINDER; |
| if (TypesUtils.isIntegralPrimitive(exprType)) { |
| r = new IntegerRemainderNode(tree, left, right); |
| |
| extendWithNodeWithException(r, arithmeticExceptionType); |
| } else { |
| r = new FloatingRemainderNode(tree, left, right); |
| } |
| } |
| break; |
| } |
| |
| case MINUS: |
| case PLUS: |
| { |
| // see JLS 15.18 |
| |
| // TypeMirror exprType = InternalUtils.typeOf(tree); |
| TypeMirror leftType = TreeUtils.typeOf(leftTree); |
| TypeMirror rightType = TreeUtils.typeOf(rightTree); |
| |
| if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { |
| assert (kind == Tree.Kind.PLUS); |
| Node left = stringConversion(scan(leftTree, p)); |
| Node right = stringConversion(scan(rightTree, p)); |
| r = new StringConcatenateNode(tree, left, right); |
| } else { |
| TypeMirror promotedType = binaryPromotedType(leftType, rightType); |
| Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); |
| Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); |
| |
| // TODO: Decide whether to deal with floating-point value |
| // set conversion. |
| if (kind == Tree.Kind.PLUS) { |
| r = new NumericalAdditionNode(tree, left, right); |
| } else { |
| assert kind == Kind.MINUS; |
| r = new NumericalSubtractionNode(tree, left, right); |
| } |
| } |
| break; |
| } |
| |
| case LEFT_SHIFT: |
| case RIGHT_SHIFT: |
| case UNSIGNED_RIGHT_SHIFT: |
| { |
| // see JLS 15.19 |
| |
| Node left = unaryNumericPromotion(scan(leftTree, p)); |
| Node right = unaryNumericPromotion(scan(rightTree, p)); |
| |
| if (kind == Tree.Kind.LEFT_SHIFT) { |
| r = new LeftShiftNode(tree, left, right); |
| } else if (kind == Tree.Kind.RIGHT_SHIFT) { |
| r = new SignedRightShiftNode(tree, left, right); |
| } else { |
| assert kind == Kind.UNSIGNED_RIGHT_SHIFT; |
| r = new UnsignedRightShiftNode(tree, left, right); |
| } |
| break; |
| } |
| |
| case GREATER_THAN: |
| case GREATER_THAN_EQUAL: |
| case LESS_THAN: |
| case LESS_THAN_EQUAL: |
| { |
| // see JLS 15.20.1 |
| TypeMirror leftType = TreeUtils.typeOf(leftTree); |
| if (TypesUtils.isBoxedPrimitive(leftType)) { |
| leftType = types.unboxedType(leftType); |
| } |
| |
| TypeMirror rightType = TreeUtils.typeOf(rightTree); |
| if (TypesUtils.isBoxedPrimitive(rightType)) { |
| rightType = types.unboxedType(rightType); |
| } |
| |
| TypeMirror promotedType = binaryPromotedType(leftType, rightType); |
| Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); |
| Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); |
| |
| Node node; |
| if (kind == Tree.Kind.GREATER_THAN) { |
| node = new GreaterThanNode(tree, left, right); |
| } else if (kind == Tree.Kind.GREATER_THAN_EQUAL) { |
| node = new GreaterThanOrEqualNode(tree, left, right); |
| } else if (kind == Tree.Kind.LESS_THAN) { |
| node = new LessThanNode(tree, left, right); |
| } else { |
| assert kind == Tree.Kind.LESS_THAN_EQUAL; |
| node = new LessThanOrEqualNode(tree, left, right); |
| } |
| |
| extendWithNode(node); |
| |
| return node; |
| } |
| |
| case EQUAL_TO: |
| case NOT_EQUAL_TO: |
| { |
| // see JLS 15.21 |
| TreeInfo leftInfo = getTreeInfo(leftTree); |
| TreeInfo rightInfo = getTreeInfo(rightTree); |
| Node left = scan(leftTree, p); |
| Node right = scan(rightTree, p); |
| |
| if (leftInfo.isNumeric() |
| && rightInfo.isNumeric() |
| && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { |
| // JLS 15.21.1 numerical equality |
| TypeMirror promotedType = |
| binaryPromotedType(leftInfo.unboxedType(), rightInfo.unboxedType()); |
| left = binaryNumericPromotion(left, promotedType); |
| right = binaryNumericPromotion(right, promotedType); |
| } else if (leftInfo.isBoolean() |
| && rightInfo.isBoolean() |
| && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { |
| // JSL 15.21.2 boolean equality |
| left = unboxAsNeeded(left, leftInfo.isBoxed()); |
| right = unboxAsNeeded(right, rightInfo.isBoxed()); |
| } |
| |
| Node node; |
| if (kind == Tree.Kind.EQUAL_TO) { |
| node = new EqualToNode(tree, left, right); |
| } else { |
| assert kind == Kind.NOT_EQUAL_TO; |
| node = new NotEqualNode(tree, left, right); |
| } |
| extendWithNode(node); |
| |
| return node; |
| } |
| |
| case AND: |
| case OR: |
| case XOR: |
| { |
| // see JLS 15.22 |
| TypeMirror leftType = TreeUtils.typeOf(leftTree); |
| TypeMirror rightType = TreeUtils.typeOf(rightTree); |
| boolean isBooleanOp = |
| TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType); |
| |
| Node left; |
| Node right; |
| |
| if (isBooleanOp) { |
| left = unbox(scan(leftTree, p)); |
| right = unbox(scan(rightTree, p)); |
| } else if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { |
| TypeMirror promotedType = binaryPromotedType(leftType, rightType); |
| left = binaryNumericPromotion(scan(leftTree, p), promotedType); |
| right = binaryNumericPromotion(scan(rightTree, p), promotedType); |
| } else { |
| left = unbox(scan(leftTree, p)); |
| right = unbox(scan(rightTree, p)); |
| } |
| |
| Node node; |
| if (kind == Tree.Kind.AND) { |
| node = new BitwiseAndNode(tree, left, right); |
| } else if (kind == Tree.Kind.OR) { |
| node = new BitwiseOrNode(tree, left, right); |
| } else { |
| assert kind == Kind.XOR; |
| node = new BitwiseXorNode(tree, left, right); |
| } |
| |
| extendWithNode(node); |
| |
| return node; |
| } |
| |
| case CONDITIONAL_AND: |
| case CONDITIONAL_OR: |
| { |
| // see JLS 15.23 and 15.24 |
| |
| // all necessary labels |
| Label rightStartL = new Label(); |
| Label shortCircuitL = new Label(); |
| |
| // left-hand side |
| Node left = scan(leftTree, p); |
| |
| ConditionalJump cjump; |
| if (kind == Tree.Kind.CONDITIONAL_AND) { |
| cjump = new ConditionalJump(rightStartL, shortCircuitL); |
| cjump.setFalseFlowRule(FlowRule.ELSE_TO_ELSE); |
| } else { |
| cjump = new ConditionalJump(shortCircuitL, rightStartL); |
| cjump.setTrueFlowRule(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: |
| throw new Error("unexpected binary tree: " + kind); |
| } |
| assert r != null : "unexpected binary tree"; |
| extendWithNode(r); |
| return r; |
| } |
| |
| @Override |
| public Node visitBlock(BlockTree tree, Void p) { |
| for (StatementTree n : tree.getStatements()) { |
| scan(n, null); |
| } |
| return null; |
| } |
| |
| @Override |
| public Node visitBreak(BreakTree tree, Void p) { |
| Name label = tree.getLabel(); |
| if (label == null) { |
| assert breakTargetL != null : "no target for break statement"; |
| |
| extendWithExtendedNode(new UnconditionalJump(breakTargetL.accessLabel())); |
| } 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); |
| builder.build(); |
| return null; |
| } |
| |
| /** Helper class for handling switch statements. */ |
| private class SwitchBuilder { |
| /** The switch tree. */ |
| private final SwitchTree switchTree; |
| /** The labels for the case bodies. */ |
| private final Label[] caseBodyLabels; |
| /** The Node for the switch expression. */ |
| private Node switchExpr; |
| |
| /** |
| * Construct a SwitchBuilder. |
| * |
| * @param tree switch tree |
| */ |
| private SwitchBuilder(SwitchTree tree) { |
| this.switchTree = tree; |
| this.caseBodyLabels = new Label[switchTree.getCases().size() + 1]; |
| } |
| |
| /** Build up the CFG for the switchTree. */ |
| public void build() { |
| TryFinallyScopeCell oldBreakTargetL = breakTargetL; |
| breakTargetL = new TryFinallyScopeCell(new Label()); |
| int cases = caseBodyLabels.length - 1; |
| for (int i = 0; i < cases; ++i) { |
| caseBodyLabels[i] = new Label(); |
| } |
| caseBodyLabels[cases] = breakTargetL.peekLabel(); |
| |
| TypeMirror switchExprType = TreeUtils.typeOf(switchTree.getExpression()); |
| VariableTree variable = |
| treeBuilder.buildVariableDecl(switchExprType, uniqueName("switch"), findOwner(), null); |
| handleArtificialTree(variable); |
| |
| VariableDeclarationNode variableNode = new VariableDeclarationNode(variable); |
| variableNode.setInSource(false); |
| extendWithNode(variableNode); |
| |
| IdentifierTree variableUse = treeBuilder.buildVariableUse(variable); |
| handleArtificialTree(variableUse); |
| |
| LocalVariableNode variableUseNode = new LocalVariableNode(variableUse); |
| variableUseNode.setInSource(false); |
| extendWithNode(variableUseNode); |
| |
| Node switchExprNode = unbox(scan(switchTree.getExpression(), null)); |
| |
| AssignmentTree assign = treeBuilder.buildAssignment(variableUse, switchTree.getExpression()); |
| handleArtificialTree(assign); |
| |
| switchExpr = new AssignmentNode(assign, variableUseNode, switchExprNode); |
| switchExpr.setInSource(false); |
| extendWithNode(switchExpr); |
| |
| extendWithNode( |
| new MarkerNode( |
| switchTree, |
| "start of switch statement #" + TreeUtils.treeUids.get(switchTree), |
| 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.peekLabel()); |
| breakTargetL = oldBreakTargetL; |
| |
| extendWithNode( |
| new MarkerNode( |
| switchTree, |
| "end of switch statement #" + TreeUtils.treeUids.get(switchTree), |
| env.getTypeUtils())); |
| } |
| |
| 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) { |
| // non-default cases |
| Node expr = scan(exprTree, null); |
| 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, null); |
| } |
| 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); |
| Node classbody = new ClassDeclarationNode(tree); |
| extendWithNode(classbody); |
| return classbody; |
| } |
| |
| @Override |
| public Node visitConditionalExpression(ConditionalExpressionTree tree, Void p) { |
| // see JLS 15.25 |
| TypeMirror exprType = TreeUtils.typeOf(tree); |
| |
| Label trueStart = new Label(); |
| Label falseStart = new Label(); |
| Label merge = new Label(); |
| |
| 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, FlowRule.BOTH_TO_THEN)); |
| |
| addLabelForNextNode(falseStart); |
| Node falseExpr = scan(tree.getFalseExpression(), p); |
| falseExpr = conditionalExprPromotion(falseExpr, exprType); |
| extendWithExtendedNode(new UnconditionalJump(merge, FlowRule.BOTH_TO_ELSE)); |
| |
| 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.accessLabel())); |
| } else { |
| assert continueLabels.containsKey(label); |
| |
| extendWithExtendedNode(new UnconditionalJump(continueLabels.get(label))); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Node visitDoWhileLoop(DoWhileLoopTree tree, Void p) { |
| Name parentLabel = getLabel(getCurrentPath()); |
| |
| Label loopEntry = new Label(); |
| Label loopExit = new Label(); |
| |
| // If the loop is a labeled statement, then its continue target is identical for continues with |
| // no label and continues with the loop's label. |
| Label conditionStart; |
| if (parentLabel != null) { |
| conditionStart = continueLabels.get(parentLabel); |
| } else { |
| conditionStart = new Label(); |
| } |
| |
| TryFinallyScopeCell oldBreakTargetL = breakTargetL; |
| breakTargetL = new TryFinallyScopeCell(loopExit); |
| |
| TryFinallyScopeCell oldContinueTargetL = continueTargetL; |
| continueTargetL = new TryFinallyScopeCell(conditionStart); |
| |
| // Loop body |
| addLabelForNextNode(loopEntry); |
| assert tree.getStatement() != null; |
| scan(tree.getStatement(), p); |
| |
| // Condition |
| addLabelForNextNode(conditionStart); |
| assert tree.getCondition() != null; |
| unbox(scan(tree.getCondition(), p)); |
| ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); |
| extendWithExtendedNode(cjump); |
| |
| // Loop exit |
| addLabelForNextNode(loopExit); |
| |
| breakTargetL = oldBreakTargetL; |
| continueTargetL = oldContinueTargetL; |
| |
| return null; |
| } |
| |
| @Override |
| public Node visitErroneous(ErroneousTree tree, Void p) { |
| throw new Error("ErroneousTree is unexpected in AST to CFG translation"); |
| } |
| |
| @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(); |
| } |
| |
| TryFinallyScopeCell oldBreakTargetL = breakTargetL; |
| breakTargetL = new TryFinallyScopeCell(loopExit); |
| |
| TryFinallyScopeCell oldContinueTargetL = continueTargetL; |
| continueTargetL = new TryFinallyScopeCell(updateStart); |
| |
| // Distinguish loops over Iterables from loops over arrays. |
| |
| VariableTree variable = tree.getVariable(); |
| VariableElement variableElement = TreeUtils.elementFromDeclaration(variable); |
| ExpressionTree expression = tree.getExpression(); |
| StatementTree statement = tree.getStatement(); |
| |
| TypeMirror exprType = TreeUtils.typeOf(expression); |
| |
| if (types.isSubtype(exprType, iterableType)) { |
| // Take the upper bound of a type variable or wildcard |
| exprType = TypesUtils.upperBound(exprType); |
| |
| assert (exprType instanceof DeclaredType) : "an Iterable must be a DeclaredType"; |
| DeclaredType declaredExprType = (DeclaredType) exprType; |
| declaredExprType.getTypeArguments(); |
| |
| MemberSelectTree iteratorSelect = treeBuilder.buildIteratorMethodAccess(expression); |
| handleArtificialTree(iteratorSelect); |
| |
| MethodInvocationTree iteratorCall = treeBuilder.buildMethodInvocation(iteratorSelect); |
| handleArtificialTree(iteratorCall); |
| |
| VariableTree iteratorVariable = |
| createEnhancedForLoopIteratorVariable(iteratorCall, variableElement); |
| handleArtificialTree(iteratorVariable); |
| |
| VariableDeclarationNode iteratorVariableDecl = new VariableDeclarationNode(iteratorVariable); |
| iteratorVariableDecl.setInSource(false); |
| |
| extendWithNode(iteratorVariableDecl); |
| |
| Node expressionNode = scan(expression, p); |
| |
| MethodAccessNode iteratorAccessNode = new MethodAccessNode(iteratorSelect, expressionNode); |
| iteratorAccessNode.setInSource(false); |
| extendWithNode(iteratorAccessNode); |
| MethodInvocationNode iteratorCallNode = |
| new MethodInvocationNode( |
| iteratorCall, iteratorAccessNode, Collections.emptyList(), getCurrentPath()); |
| iteratorCallNode.setInSource(false); |
| extendWithNode(iteratorCallNode); |
| |
| translateAssignment( |
| iteratorVariable, new LocalVariableNode(iteratorVariable), iteratorCallNode); |
| |
| // Test the loop ending condition |
| addLabelForNextNode(conditionStart); |
| IdentifierTree iteratorUse1 = treeBuilder.buildVariableUse(iteratorVariable); |
| handleArtificialTree(iteratorUse1); |
| |
| LocalVariableNode iteratorReceiverNode = new LocalVariableNode(iteratorUse1); |
| iteratorReceiverNode.setInSource(false); |
| extendWithNode(iteratorReceiverNode); |
| |
| MemberSelectTree hasNextSelect = treeBuilder.buildHasNextMethodAccess(iteratorUse1); |
| handleArtificialTree(hasNextSelect); |
| |
| MethodAccessNode hasNextAccessNode = |
| new MethodAccessNode(hasNextSelect, iteratorReceiverNode); |
| hasNextAccessNode.setInSource(false); |
| extendWithNode(hasNextAccessNode); |
| |
| MethodInvocationTree hasNextCall = treeBuilder.buildMethodInvocation(hasNextSelect); |
| handleArtificialTree(hasNextCall); |
| |
| MethodInvocationNode hasNextCallNode = |
| new MethodInvocationNode( |
| hasNextCall, hasNextAccessNode, Collections.emptyList(), getCurrentPath()); |
| hasNextCallNode.setInSource(false); |
| extendWithNode(hasNextCallNode); |
| extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); |
| |
| // Loop body, starting with declaration of the loop iteration variable |
| addLabelForNextNode(loopEntry); |
| extendWithNode(new VariableDeclarationNode(variable)); |
| |
| IdentifierTree iteratorUse2 = treeBuilder.buildVariableUse(iteratorVariable); |
| handleArtificialTree(iteratorUse2); |
| |
| LocalVariableNode iteratorReceiverNode2 = new LocalVariableNode(iteratorUse2); |
| iteratorReceiverNode2.setInSource(false); |
| extendWithNode(iteratorReceiverNode2); |
| |
| MemberSelectTree nextSelect = treeBuilder.buildNextMethodAccess(iteratorUse2); |
| handleArtificialTree(nextSelect); |
| |
| MethodAccessNode nextAccessNode = new MethodAccessNode(nextSelect, iteratorReceiverNode2); |
| nextAccessNode.setInSource(false); |
| extendWithNode(nextAccessNode); |
| |
| MethodInvocationTree nextCall = treeBuilder.buildMethodInvocation(nextSelect); |
| handleArtificialTree(nextCall); |
| |
| MethodInvocationNode nextCallNode = |
| new MethodInvocationNode( |
| nextCall, nextAccessNode, Collections.emptyList(), getCurrentPath()); |
| // If the type of iteratorVariable is a capture, its type tree may be missing annotations, so |
| // save the expression in the node so that the full type can be found later. |
| nextCallNode.setIterableExpression(expression); |
| nextCallNode.setInSource(false); |
| extendWithNode(nextCallNode); |
| |
| AssignmentNode assignNode = |
| translateAssignment(variable, new LocalVariableNode(variable), nextCall); |
| // translateAssignment() scans variable and creates new nodes, so set the expression |
| // there, too. |
| ((MethodInvocationNode) assignNode.getExpression()).setIterableExpression(expression); |
| |
| assert statement != null; |
| scan(statement, p); |
| |
| // Loop back edge |
| addLabelForNextNode(updateStart); |
| extendWithExtendedNode(new UnconditionalJump(conditionStart)); |
| |
| } else { |
| // TODO: Shift any labels after the initialization of the |
| // temporary array variable. |
| |
| VariableTree arrayVariable = createEnhancedForLoopArrayVariable(expression, variableElement); |
| handleArtificialTree(arrayVariable); |
| |
| VariableDeclarationNode arrayVariableNode = new VariableDeclarationNode(arrayVariable); |
| arrayVariableNode.setInSource(false); |
| extendWithNode(arrayVariableNode); |
| Node expressionNode = scan(expression, p); |
| |
| translateAssignment(arrayVariable, new LocalVariableNode(arrayVariable), expressionNode); |
| |
| // Declare and initialize the loop index variable |
| TypeMirror intType = types.getPrimitiveType(TypeKind.INT); |
| |
| LiteralTree zero = treeBuilder.buildLiteral(Integer.valueOf(0)); |
| handleArtificialTree(zero); |
| |
| VariableTree indexVariable = |
| treeBuilder.buildVariableDecl( |
| intType, uniqueName("index"), variableElement.getEnclosingElement(), zero); |
| handleArtificialTree(indexVariable); |
| VariableDeclarationNode indexVariableNode = new VariableDeclarationNode(indexVariable); |
| indexVariableNode.setInSource(false); |
| extendWithNode(indexVariableNode); |
| IntegerLiteralNode zeroNode = new IntegerLiteralNode(zero); |
| extendWithNode(zeroNode); |
| |
| translateAssignment(indexVariable, new LocalVariableNode(indexVariable), zeroNode); |
| |
| // Compare index to array length |
| addLabelForNextNode(conditionStart); |
| IdentifierTree indexUse1 = treeBuilder.buildVariableUse(indexVariable); |
| handleArtificialTree(indexUse1); |
| LocalVariableNode indexNode1 = new LocalVariableNode(indexUse1); |
| indexNode1.setInSource(false); |
| extendWithNode(indexNode1); |
| |
| IdentifierTree arrayUse1 = treeBuilder.buildVariableUse(arrayVariable); |
| handleArtificialTree(arrayUse1); |
| LocalVariableNode arrayNode1 = new LocalVariableNode(arrayUse1); |
| extendWithNode(arrayNode1); |
| |
| MemberSelectTree lengthSelect = treeBuilder.buildArrayLengthAccess(arrayUse1); |
| handleArtificialTree(lengthSelect); |
| FieldAccessNode lengthAccessNode = new FieldAccessNode(lengthSelect, arrayNode1); |
| lengthAccessNode.setInSource(false); |
| extendWithNode(lengthAccessNode); |
| |
| BinaryTree lessThan = treeBuilder.buildLessThan(indexUse1, lengthSelect); |
| handleArtificialTree(lessThan); |
| |
| LessThanNode lessThanNode = new LessThanNode(lessThan, indexNode1, lengthAccessNode); |
| lessThanNode.setInSource(false); |
| extendWithNode(lessThanNode); |
| extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); |
| |
| // Loop body, starting with declaration of the loop iteration variable |
| addLabelForNextNode(loopEntry); |
| extendWithNode(new VariableDeclarationNode(variable)); |
| |
| IdentifierTree arrayUse2 = treeBuilder.buildVariableUse(arrayVariable); |
| handleArtificialTree(arrayUse2); |
| LocalVariableNode arrayNode2 = new LocalVariableNode(arrayUse2); |
| arrayNode2.setInSource(false); |
| extendWithNode(arrayNode2); |
| |
| IdentifierTree indexUse2 = treeBuilder.buildVariableUse(indexVariable); |
| handleArtificialTree(indexUse2); |
| LocalVariableNode indexNode2 = new LocalVariableNode(indexUse2); |
| indexNode2.setInSource(false); |
| extendWithNode(indexNode2); |
| |
| ArrayAccessTree arrayAccess = treeBuilder.buildArrayAccess(arrayUse2, indexUse2); |
| handleArtificialTree(arrayAccess); |
| ArrayAccessNode arrayAccessNode = new ArrayAccessNode(arrayAccess, arrayNode2, indexNode2); |
| arrayAccessNode.setArrayExpression(expression); |
| arrayAccessNode.setInSource(false); |
| extendWithNode(arrayAccessNode); |
| AssignmentNode arrayAccessAssignNode = |
| translateAssignment(variable, new LocalVariableNode(variable), arrayAccessNode); |
| extendWithNodeWithException(arrayAccessNode, nullPointerExceptionType); |
| // translateAssignment() scans variable and creates new nodes, so set the expression |
| // there, too. |
| Node arrayAccessAssignNodeExpr = arrayAccessAssignNode.getExpression(); |
| if (arrayAccessAssignNodeExpr instanceof ArrayAccessNode) { |
| ((ArrayAccessNode) arrayAccessAssignNodeExpr).setArrayExpression(expression); |
| } else if (arrayAccessAssignNodeExpr instanceof MethodInvocationNode) { |
| // If the array component type is a primitive, there may be a boxing or unboxing |
| // conversion. Treat that as an iterator. |
| MethodInvocationNode boxingNode = (MethodInvocationNode) arrayAccessAssignNodeExpr; |
| boxingNode.setIterableExpression(expression); |
| } |
| |
| assert statement != null; |
| scan(statement, p); |
| |
| // Loop back edge |
| addLabelForNextNode(updateStart); |
| |
| IdentifierTree indexUse3 = treeBuilder.buildVariableUse(indexVariable); |
| handleArtificialTree(indexUse3); |
| LocalVariableNode indexNode3 = new LocalVariableNode(indexUse3); |
| indexNode3.setInSource(false); |
| extendWithNode(indexNode3); |
| |
| LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); |
| handleArtificialTree(oneTree); |
| Node one = new IntegerLiteralNode(oneTree); |
| one.setInSource(false); |
| extendWithNode(one); |
| |
| BinaryTree addOneTree = treeBuilder.buildBinary(intType, Tree.Kind.PLUS, indexUse3, oneTree); |
| handleArtificialTree(addOneTree); |
| Node addOneNode = new NumericalAdditionNode(addOneTree, indexNode3, one); |
| addOneNode.setInSource(false); |
| extendWithNode(addOneNode); |
| |
| AssignmentTree assignTree = treeBuilder.buildAssignment(indexUse3, addOneTree); |
| handleArtificialTree(assignTree); |
| Node assignNode = new AssignmentNode(assignTree, indexNode3, addOneNode); |
| assignNode.setInSource(false); |
| extendWithNode(assignNode); |
| |
| extendWithExtendedNode(new UnconditionalJump(conditionStart)); |
| } |
| |
| // Loop exit |
| addLabelForNextNode(loopExit); |
| |
| breakTargetL = oldBreakTargetL; |
| continueTargetL = oldContinueTargetL; |
| |
| return null; |
| } |
| |
| protected VariableTree createEnhancedForLoopIteratorVariable( |
| MethodInvocationTree iteratorCall, VariableElement variableElement) { |
| TypeMirror iteratorType = TreeUtils.typeOf(iteratorCall); |
| |
| // Declare and initialize a new, unique iterator variable |
| VariableTree iteratorVariable = |
| treeBuilder.buildVariableDecl( |
| iteratorType, // annotatedIteratorTypeTree, |
| uniqueName("iter"), |
| variableElement.getEnclosingElement(), |
| iteratorCall); |
| return iteratorVariable; |
| } |
| |
| protected VariableTree createEnhancedForLoopArrayVariable( |
| ExpressionTree expression, VariableElement variableElement) { |
| TypeMirror arrayType = TreeUtils.typeOf(expression); |
| |
| // Declare and initialize a temporary array variable |
| VariableTree arrayVariable = |
| treeBuilder.buildVariableDecl( |
| arrayType, uniqueName("array"), variableElement.getEnclosingElement(), expression); |
| return arrayVariable; |
| } |
| |
| @Override |
| public Node visitForLoop(ForLoopTree tree, Void p) { |
| Name parentLabel = getLabel(getCurrentPath()); |
| |
| Label conditionStart = new Label(); |
| Label loopEntry = new Label(); |
| Label loopExit = new Label(); |
| |
| // If the loop is a labeled statement, then its continue target is identical for continues with |
| // no label and continues with the loop's label. |
| Label updateStart; |
| if (parentLabel != null) { |
| updateStart = continueLabels.get(parentLabel); |
| } else { |
| updateStart = new Label(); |
| } |
| |
| TryFinallyScopeCell oldBreakTargetL = breakTargetL; |
| breakTargetL = new TryFinallyScopeCell(loopExit); |
| |
| TryFinallyScopeCell oldContinueTargetL = continueTargetL; |
| continueTargetL = new TryFinallyScopeCell(updateStart); |
| |
| // Initializer |
| for (StatementTree init : tree.getInitializer()) { |
| scan(init, p); |
| } |
| |
| // Condition |
| addLabelForNextNode(conditionStart); |
| if (tree.getCondition() != null) { |
| unbox(scan(tree.getCondition(), p)); |
| ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); |
| extendWithExtendedNode(cjump); |
| } |
| |
| // Loop body |
| addLabelForNextNode(loopEntry); |
| assert tree.getStatement() != null; |
| scan(tree.getStatement(), p); |
| |
| // Update |
| addLabelForNextNode(updateStart); |
| for (ExpressionStatementTree update : tree.getUpdate()) { |
| scan(update, p); |
| } |
| |
| extendWithExtendedNode(new UnconditionalJump(conditionStart)); |
| |
| // Loop exit |
| addLabelForNextNode(loopExit); |
| |
| breakTargetL = oldBreakTargetL; |
| continueTargetL = oldContinueTargetL; |
| |
| return null; |
| } |
| |
| @Override |
| public Node visitIdentifier(IdentifierTree tree, Void p) { |
| Node node; |
| if (TreeUtils.isFieldAccess(tree)) { |
| Node receiver = getReceiver(tree); |
| node = new FieldAccessNode(tree, receiver); |
| } else { |
| Element element = TreeUtils.elementFromUse(tree); |
| switch (element.getKind()) { |
| case FIELD: |
| // Note that "this"/"super" is a field, but not a field access. |
| if (element.getSimpleName().contentEquals("this")) { |
| node = new ExplicitThisNode(tree); |
| } else { |
| node = new SuperNode(tree); |
| } |
| break; |
| case EXCEPTION_PARAMETER: |
| case LOCAL_VARIABLE: |
| case RESOURCE_VARIABLE: |
| case PARAMETER: |
| node = new LocalVariableNode(tree); |
| break; |
| case PACKAGE: |
| node = new PackageNameNode(tree); |
| break; |
| default: |
| if (ElementUtils.isTypeDeclaration(element)) { |
| node = new ClassNameNode(tree); |
| break; |
| } else if (ElementUtils.isBindingVariable(element)) { |
| // Note: BINDING_VARIABLE should be added as a direct case above when instanceof pattern |
| // matching and Java15 are supported. |
| node = new LocalVariableNode(tree); |
| break; |
| } |
| throw new BugInCF("bad element kind " + element.getKind()); |
| } |
| } |
| if (node instanceof ClassNameNode) { |
| extendWithClassNameNode((ClassNameNode) node); |
| } else { |
| extendWithNode(node); |
| } |
| return node; |
| } |
| |
| @Override |
| public Node visitIf(IfTree tree, Void p) { |
| // all necessary labels |
| Label thenEntry = new Label(); |
| Label elseEntry = new Label(); |
| Label endIf = new Label(); |
| |
| // basic block for the condition |
| unbox(scan(tree.getCondition(), p)); |
| |
| ConditionalJump cjump = new ConditionalJump(thenEntry, elseEntry); |
| extendWithExtendedNode(cjump); |
| |
| // then branch |
| addLabelForNextNode(thenEntry); |
| StatementTree thenStatement = tree.getThenStatement(); |
| scan(thenStatement, p); |
| extendWithExtendedNode(new UnconditionalJump(endIf)); |
| |
| // else branch |
| addLabelForNextNode(elseEntry); |
| StatementTree elseStatement = tree.getElseStatement(); |
| if (elseStatement != null) { |
| scan(elseStatement, p); |
| } |
| |
| // label the end of the if statement |
| addLabelForNextNode(endIf); |
| |
| return null; |
| } |
| |
| @Override |
| public Node visitImport(ImportTree tree, Void p) { |
| throw new Error("ImportTree is unexpected in AST to CFG translation"); |
| } |
| |
| @Override |
| public Node visitArrayAccess(ArrayAccessTree tree, Void p) { |
| Node array = scan(tree.getExpression(), p); |
| Node index = unaryNumericPromotion(scan(tree.getIndex(), p)); |
| Node arrayAccess = new ArrayAccessNode(tree, array, index); |
| extendWithNode(arrayAccess); |
| extendWithNodeWithException(arrayAccess, arrayIndexOutOfBoundsExceptionType); |
| extendWithNodeWithException(arrayAccess, nullPointerExceptionType); |
| return arrayAccess; |
| } |
| |
| @Override |
| public Node visitLabeledStatement(LabeledStatementTree tree, Void p) { |
| // This method can set the break target after generating all Nodes in the contained statement, |
| // but it can't set the continue target, which may be in the middle of a sequence of |
| // nodes. Labeled loops must look up and use the continue Labels. |
| Name labelName = tree.getLabel(); |
| |
| Label 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: |
| throw new Error("unexpected literal tree"); |
| } |
| assert r != null : "unexpected literal tree"; |
| extendWithNode(r); |
| return r; |
| } |
| |
| @Override |
| public Node visitMethod(MethodTree tree, Void p) { |
| throw new Error("MethodTree is unexpected in AST to CFG translation"); |
| } |
| |
| @Override |
| public Node visitModifiers(ModifiersTree tree, Void p) { |
| throw new Error("ModifiersTree is unexpected in AST to CFG translation"); |
| } |
| |
| @Override |
| public Node visitNewArray(NewArrayTree tree, Void p) { |
| // see JLS 15.10 |
| |
| ArrayType type = (ArrayType) TreeUtils.typeOf(tree); |
| TypeMirror elemType = type.getComponentType(); |
| |
| List<? extends ExpressionTree> dimensions = tree.getDimensions(); |
| List<? extends ExpressionTree> initializers = tree.getInitializers(); |
| assert dimensions != null; |
| |
| List<Node> dimensionNodes = |
| CollectionsPlume.mapList(dim -> unaryNumericPromotion(scan(dim, p)), dimensions); |
| |
| List<Node> initializerNodes; |
| if (initializers == null) { |
| initializerNodes = Collections.emptyList(); |
| } else { |
| initializerNodes = |
| CollectionsPlume.mapList(init -> assignConvert(scan(init, p), elemType), initializers); |
| } |
| |
| Node node = new ArrayCreationNode(tree, type, dimensionNodes, initializerNodes); |
| |
| extendWithNodeWithExceptions(node, newArrayExceptionTypes); |
| return node; |
| } |
| |
| @Override |
| public Node visitNewClass(NewClassTree tree, Void p) { |
| // see JLS 15.9 |
| |
| Tree enclosingExpr = tree.getEnclosingExpression(); |
| if (enclosingExpr != null) { |
| scan(enclosingExpr, p); |
| } |
| |
| // Convert constructor arguments |
| ExecutableElement constructor = TreeUtils.elementFromUse(tree); |
| |
| List<? extends ExpressionTree> actualExprs = tree.getArguments(); |
| |
| List<Node> arguments = convertCallArguments(constructor, actualExprs); |
| |
| // TODO: for anonymous classes, don't use the identifier alone. |
| // See Issue 890. |
| Node constructorNode = scan(tree.getIdentifier(), p); |
| |
| // Handle anonymous classes in visitClass. |
| // Note that getClassBody() and therefore classbody can be null. |
| ClassDeclarationNode classbody = (ClassDeclarationNode) scan(tree.getClassBody(), p); |
| |
| Node node = new ObjectCreationNode(tree, constructorNode, arguments, classbody); |
| |
| List<? extends TypeMirror> thrownTypes = constructor.getThrownTypes(); |
| Set<TypeMirror> thrownSet = |
| new LinkedHashSet<>(thrownTypes.size() + uncheckedExceptionTypes.size()); |
| // Add exceptions explicitly mentioned in the throws clause. |
| thrownSet.addAll(thrownTypes); |
| // Add types to account for unchecked exceptions |
| thrownSet.addAll(uncheckedExceptionTypes); |
| |
| extendWithNodeWithExceptions(node, thrownSet); |
| |
| return node; |
| } |
| |
| /** |
| * Maps a {@code Tree} to its directly enclosing {@code ParenthesizedTree} if one exists. |
| * |
| * <p>This map is used by {@link CFGTranslationPhaseOne#addToLookupMap(Node)} to associate a |
| * {@code ParenthesizedTree} with the dataflow {@code Node} that was used during inference. This |
| * map is necessary because dataflow does not create a {@code Node} for a {@code |
| * ParenthesizedTree}. |
| */ |
| private final Map<Tree, ParenthesizedTree> 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 = |
| TreePathUtil.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(this.returnTargetL.accessLabel())); |
| |
| return result; |
| } |
| |
| @Override |
| public Node visitMemberSelect(MemberSelectTree tree, Void p) { |
| Node expr = scan(tree.getExpression(), p); |
| if (!TreeUtils.isFieldAccess(tree)) { |
| // Could be a selector of a class or package |
| Element element = TreeUtils.elementFromUse(tree); |
| if (ElementUtils.isTypeElement(element)) { |
| ClassNameNode result = new ClassNameNode(tree, expr); |
| extendWithClassNameNode(result); |
| return result; |
| } else if (element.getKind() == ElementKind.PACKAGE) { |
| Node result = new PackageNameNode(tree, (PackageNameNode) expr); |
| extendWithNode(result); |
| return result; |
| } else { |
| throw new Error("Unexpected element kind: " + element.getKind()); |
| } |
| } |
| |
| Node node = new FieldAccessNode(tree, expr); |
| |
| Element element = TreeUtils.elementFromUse(tree); |
| if (ElementUtils.isStatic(element) |
| || expr instanceof ImplicitThisNode |
| || expr instanceof ExplicitThisNode) { |
| // No NullPointerException can be thrown, use normal node |
| extendWithNode(node); |
| } else { |
| extendWithNodeWithException(node, nullPointerExceptionType); |
| } |
| |
| return node; |
| } |
| |
| @Override |
| public Node visitEmptyStatement(EmptyStatementTree tree, Void p) { |
| return null; |
| } |
| |
| @Override |
| public Node visitSynchronized(SynchronizedTree tree, Void p) { |
| // see JLS 14.19 |
| |
| Node synchronizedExpr = scan(tree.getExpression(), p); |
| SynchronizedNode synchronizedStartNode = |
| new SynchronizedNode(tree, synchronizedExpr, true, env.getTypeUtils()); |
| extendWithNode(synchronizedStartNode); |
| scan(tree.getBlock(), p); |
| SynchronizedNode synchronizedEndNode = |
| new SynchronizedNode(tree, synchronizedExpr, false, env.getTypeUtils()); |
| extendWithNode(synchronizedEndNode); |
| |
| return null; |
| } |
| |
| @Override |
| public Node visitThrow(ThrowTree tree, Void p) { |
| Node expression = scan(tree.getExpression(), p); |
| TypeMirror exception = expression.getType(); |
| ThrowNode throwsNode = new ThrowNode(tree, expression, env.getTypeUtils()); |
| NodeWithExceptionsHolder exNode = extendWithNodeWithException(throwsNode, exception); |
| exNode.setTerminatesExecution(true); |
| return throwsNode; |
| } |
| |
| @Override |
| public Node visitCompilationUnit(CompilationUnitTree tree, Void p) { |
| throw new Error("CompilationUnitTree is unexpected in AST to CFG translation"); |
| } |
| |
| @Override |
| public Node visitTry(TryTree tree, Void p) { |
| List<? extends CatchTree> catches = tree.getCatches(); |
| BlockTree finallyBlock = tree.getFinallyBlock(); |
| |
| extendWithNode( |
| new MarkerNode( |
| tree, "start of try statement #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); |
| |
| List<Pair<TypeMirror, Label>> catchLabels = |
| CollectionsPlume.mapList( |
| (CatchTree c) -> { |
| return Pair.of(TreeUtils.typeOf(c.getParameter().getType()), new Label()); |
| }, |
| catches); |
| |
| // Store return/break/continue labels, just in case we need them for a finally block. |
| TryFinallyScopeCell oldReturnTargetL = returnTargetL; |
| TryFinallyScopeCell oldBreakTargetL = breakTargetL; |
| Map<Name, Label> oldBreakLabels = breakLabels; |
| TryFinallyScopeCell oldContinueTargetL = continueTargetL; |
| Map<Name, Label> oldContinueLabels = continueLabels; |
| |
| Label finallyLabel = null; |
| Label exceptionalFinallyLabel = null; |
| |
| if (finallyBlock != null) { |
| finallyLabel = new Label(); |
| |
| exceptionalFinallyLabel = new Label(); |
| tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel)); |
| |
| returnTargetL = new TryFinallyScopeCell(); |
| |
| breakTargetL = new TryFinallyScopeCell(); |
| breakLabels = new TryFinallyScopeMap(); |
| |
| continueTargetL = new TryFinallyScopeCell(); |
| continueLabels = new TryFinallyScopeMap(); |
| } |
| |
| Label doneLabel = new Label(); |
| |
| tryStack.pushFrame(new TryCatchFrame(types, catchLabels)); |
| |
| // Must scan the resources *after* we push frame to tryStack. Otherwise we can lose catch |
| // blocks. |
| // TODO: Should we handle try-with-resources blocks by also generating code for automatically |
| // closing the resources? |
| List<? extends Tree> resources = tree.getResources(); |
| for (Tree resource : resources) { |
| scan(resource, p); |
| } |
| |
| extendWithNode( |
| new MarkerNode( |
| tree, "start of try block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); |
| scan(tree.getBlock(), p); |
| extendWithNode( |
| new MarkerNode( |
| tree, "end of try block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); |
| |
| extendWithExtendedNode(new UnconditionalJump(CFGBuilder.firstNonNull(finallyLabel, doneLabel))); |
| |
| tryStack.popFrame(); |
| |
| int catchIndex = 0; |
| for (CatchTree c : catches) { |
| addLabelForNextNode(catchLabels.get(catchIndex).second); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "start of catch block for " |
| + c.getParameter().getType() |
| + " #" |
| + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| scan(c, p); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "end of catch block for " |
| + c.getParameter().getType() |
| + " #" |
| + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| |
| catchIndex++; |
| extendWithExtendedNode( |
| new UnconditionalJump(CFGBuilder.firstNonNull(finallyLabel, doneLabel))); |
| } |
| |
| if (finallyLabel != null) { |
| // Reset values before analyzing the finally block! |
| |
| tryStack.popFrame(); |
| |
| { // Scan 'finallyBlock' for only 'finallyLabel' (a successful path) |
| addLabelForNextNode(finallyLabel); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "start of finally block #" + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| scan(finallyBlock, p); |
| extendWithNode( |
| new MarkerNode( |
| tree, "end of finally block #" + TreeUtils.treeUids.get(tree), env.getTypeUtils())); |
| extendWithExtendedNode(new UnconditionalJump(doneLabel)); |
| } |
| |
| if (hasExceptionalPath(exceptionalFinallyLabel)) { |
| // If an exceptional path exists, scan 'finallyBlock' for 'exceptionalFinallyLabel', and |
| // scan copied 'finallyBlock' for 'finallyLabel' (a successful path). If there is no |
| // successful path, it will be removed in later phase. |
| // TODO: Don't we need a separate finally block for each kind of exception? |
| addLabelForNextNode(exceptionalFinallyLabel); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "start of finally block for Throwable #" + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| |
| scan(finallyBlock, p); |
| |
| NodeWithExceptionsHolder throwing = |
| extendWithNodeWithException( |
| new MarkerNode( |
| tree, |
| "end of finally block for Throwable #" + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils()), |
| throwableType); |
| |
| throwing.setTerminatesExecution(true); |
| } |
| |
| if (returnTargetL.wasAccessed()) { |
| addLabelForNextNode(returnTargetL.peekLabel()); |
| returnTargetL = oldReturnTargetL; |
| |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "start of finally block for return #" + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| scan(finallyBlock, p); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "end of finally block for return #" + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| extendWithExtendedNode(new UnconditionalJump(returnTargetL.accessLabel())); |
| } else { |
| returnTargetL = oldReturnTargetL; |
| } |
| |
| if (breakTargetL.wasAccessed()) { |
| addLabelForNextNode(breakTargetL.peekLabel()); |
| breakTargetL = oldBreakTargetL; |
| |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "start of finally block for break #" + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| scan(finallyBlock, p); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "end of finally block for break #" + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| extendWithExtendedNode(new UnconditionalJump(breakTargetL.accessLabel())); |
| } else { |
| breakTargetL = oldBreakTargetL; |
| } |
| |
| Map<Name, Label> accessedBreakLabels = ((TryFinallyScopeMap) breakLabels).getAccessedNames(); |
| if (!accessedBreakLabels.isEmpty()) { |
| breakLabels = oldBreakLabels; |
| |
| for (Map.Entry<Name, Label> access : accessedBreakLabels.entrySet()) { |
| addLabelForNextNode(access.getValue()); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "start of finally block for break label " |
| + access.getKey() |
| + " #" |
| + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| scan(finallyBlock, p); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "end of finally block for break label " |
| + access.getKey() |
| + " #" |
| + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| extendWithExtendedNode(new UnconditionalJump(breakLabels.get(access.getKey()))); |
| } |
| } else { |
| breakLabels = oldBreakLabels; |
| } |
| |
| if (continueTargetL.wasAccessed()) { |
| addLabelForNextNode(continueTargetL.peekLabel()); |
| continueTargetL = oldContinueTargetL; |
| |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "start of finally block for continue #" + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| scan(finallyBlock, p); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "end of finally block for continue #" + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| extendWithExtendedNode(new UnconditionalJump(continueTargetL.accessLabel())); |
| } else { |
| continueTargetL = oldContinueTargetL; |
| } |
| |
| Map<Name, Label> accessedContinueLabels = |
| ((TryFinallyScopeMap) continueLabels).getAccessedNames(); |
| if (!accessedContinueLabels.isEmpty()) { |
| continueLabels = oldContinueLabels; |
| |
| for (Map.Entry<Name, Label> access : accessedContinueLabels.entrySet()) { |
| addLabelForNextNode(access.getValue()); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "start of finally block for continue label " |
| + access.getKey() |
| + " #" |
| + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| scan(finallyBlock, p); |
| extendWithNode( |
| new MarkerNode( |
| tree, |
| "end of finally block for continue label " |
| + access.getKey() |
| + " #" |
| + TreeUtils.treeUids.get(tree), |
| env.getTypeUtils())); |
| extendWithExtendedNode(new UnconditionalJump(continueLabels.get(access.getKey()))); |
| } |
| } else { |
| continueLabels = oldContinueLabels; |
| } |
| } |
| |
| addLabelForNextNode(doneLabel); |
| |
| return null; |
| } |
| |
| /** |
| * Returns whether an exceptional node for {@code target} exists in {@link #nodeList} or not. |
| * |
| * @param target label for exception |
| * @return true when an exceptional node for {@code target} exists in {@link #nodeList} |
| */ |
| private boolean hasExceptionalPath(Label target) { |
| for (ExtendedNode node : nodeList) { |
| if (node instanceof NodeWithExceptionsHolder) { |
| NodeWithExceptionsHolder exceptionalNode = (NodeWithExceptionsHolder) node; |
| for (Set<Label> labels : exceptionalNode.getExceptions().values()) { |
| if (labels.contains(target)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public Node visitParameterizedType(ParameterizedTypeTree tree, Void p) { |
| Node result = new ParameterizedTypeNode(tree); |
| extendWithNode(result); |
| return result; |
| } |
| |
| @Override |
| public Node visitUnionType(UnionTypeTree tree, Void p) { |
| throw new Error("UnionTypeTree is unexpected in AST to CFG translation"); |
| } |
| |
| @Override |
| public Node visitArrayType(ArrayTypeTree tree, Void p) { |
| Node result = new ArrayTypeNode(tree, types); |
| extendWithNode(new ArrayTypeNode(tree, types)); |
| return result; |
| } |
| |
| @Override |
| public Node visitTypeCast(TypeCastTree tree, Void p) { |
| final Node operand = scan(tree.getExpression(), p); |
| final TypeMirror type = TreeUtils.typeOf(tree.getType()); |
| final Node node = new TypeCastNode(tree, operand, type, types); |
| |
| extendWithNodeWithException(node, classCastExceptionType); |
| return node; |
| } |
| |
| @Override |
| public Node visitPrimitiveType(PrimitiveTypeTree tree, Void p) { |
| Node result = new PrimitiveTypeNode(tree, types); |
| extendWithNode(result); |
| return result; |
| } |
| |
| @Override |
| public Node visitTypeParameter(TypeParameterTree tree, Void p) { |
| throw new Error("TypeParameterTree is unexpected in AST to CFG translation"); |
| } |
| |
| @Override |
| public Node visitInstanceOf(InstanceOfTree tree, Void p) { |
| Node operand = scan(tree.getExpression(), p); |
| TypeMirror refType = TreeUtils.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 = new BitwiseComplementNode(tree, expr); |
| break; |
| case UNARY_MINUS: |
| result = new NumericalMinusNode(tree, expr); |
| break; |
| case UNARY_PLUS: |
| result = new NumericalPlusNode(tree, expr); |
| break; |
| default: |
| throw new Error("Unexpected kind"); |
| } |
| extendWithNode(result); |
| break; |
| } |
| |
| case LOGICAL_COMPLEMENT: |
| { |
| // see JLS 15.15.6 |
| Node expr = scan(tree.getExpression(), p); |
| result = new ConditionalNotNode(tree, unbox(expr)); |
| extendWithNode(result); |
| break; |
| } |
| |
| case POSTFIX_DECREMENT: |
| case POSTFIX_INCREMENT: |
| case PREFIX_DECREMENT: |
| case PREFIX_INCREMENT: |
| { |
| ExpressionTree exprTree = tree.getExpression(); |
| Node expr = scan(exprTree, p); |
| |
| boolean isIncrement = |
| kind == Tree.Kind.POSTFIX_INCREMENT || kind == Kind.PREFIX_INCREMENT; |
| boolean isPostfix = kind == Tree.Kind.POSTFIX_INCREMENT || kind == Kind.POSTFIX_DECREMENT; |
| AssignmentNode unaryAssign = |
| createIncrementOrDecrementAssign(isPostfix ? null : tree, expr, isIncrement); |
| addToUnaryAssignLookupMap(tree, unaryAssign); |
| |
| if (isPostfix) { |
| TypeMirror exprType = TreeUtils.typeOf(exprTree); |
| VariableTree tempVarDecl = |
| treeBuilder.buildVariableDecl( |
| exprType, uniqueName("tempPostfix"), findOwner(), tree.getExpression()); |
| handleArtificialTree(tempVarDecl); |
| VariableDeclarationNode tempVarDeclNode = new VariableDeclarationNode(tempVarDecl); |
| tempVarDeclNode.setInSource(false); |
| extendWithNode(tempVarDeclNode); |
| |
| Tree tempVar = treeBuilder.buildVariableUse(tempVarDecl); |
| handleArtificialTree(tempVar); |
| Node tempVarNode = new LocalVariableNode(tempVar); |
| tempVarNode.setInSource(false); |
| extendWithNode(tempVarNode); |
| |
| AssignmentNode tempAssignNode = new AssignmentNode(tree, tempVarNode, expr); |
| tempAssignNode.setInSource(false); |
| extendWithNode(tempAssignNode); |
| |
| Tree resultExpr = treeBuilder.buildVariableUse(tempVarDecl); |
| handleArtificialTree(resultExpr); |
| result = new LocalVariableNode(resultExpr); |
| result.setInSource(false); |
| extendWithNode(result); |
| } else { |
| result = unaryAssign; |
| } |
| break; |
| } |
| |
| case OTHER: |
| default: |
| // special node NLLCHK |
| if (tree.toString().startsWith("<*nullchk*>")) { |
| Node expr = scan(tree.getExpression(), p); |
| result = new NullChkNode(tree, expr); |
| extendWithNode(result); |
| break; |
| } |
| |
| throw new Error("Unknown kind (" + kind + ") of unary expression: " + tree); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Create assignment node which represent increment or decrement. |
| * |
| * @param target tree for assignment node. If it's null, corresponding assignment tree will be |
| * generated. |
| * @param expr expression node to be incremented or decremented |
| * @param isIncrement true when it's increment |
| * @return assignment node for corresponding increment or decrement |
| */ |
| private AssignmentNode createIncrementOrDecrementAssign( |
| Tree target, Node expr, boolean isIncrement) { |
| ExpressionTree exprTree = (ExpressionTree) expr.getTree(); |
| TypeMirror exprType = expr.getType(); |
| TypeMirror oneType = types.getPrimitiveType(TypeKind.INT); |
| 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, isIncrement ? Tree.Kind.PLUS : Tree.Kind.MINUS, exprTree, oneTree); |
| handleArtificialTree(operTree); |
| |
| Node operNode; |
| if (isIncrement) { |
| operNode = new NumericalAdditionNode(operTree, exprRHS, one); |
| } else { |
| operNode = new NumericalSubtractionNode(operTree, exprRHS, one); |
| } |
| operNode.setInSource(false); |
| extendWithNode(operNode); |
| |
| Node narrowed = narrowAndBox(operNode, exprType); |
| |
| if (target == null) { |
| target = treeBuilder.buildAssignment(exprTree, (ExpressionTree) narrowed.getTree()); |
| handleArtificialTree(target); |
| } |
| |
| AssignmentNode assignNode = new AssignmentNode(target, expr, narrowed); |
| assignNode.setInSource(false); |
| extendWithNode(assignNode); |
| return assignNode; |
| } |
| |
| @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 = TreePathUtil.enclosingClass(getCurrentPath()); |
| TypeElement classElem = TreeUtils.elementFromDeclaration(enclosingClass); |
| Node receiver = new ImplicitThisNode(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(); |
| } |
| |
| TryFinallyScopeCell oldBreakTargetL = breakTargetL; |
| breakTargetL = new TryFinallyScopeCell(loopExit); |
| |
| TryFinallyScopeCell oldContinueTargetL = continueTargetL; |
| continueTargetL = new TryFinallyScopeCell(conditionStart); |
| |
| // Condition |
| addLabelForNextNode(conditionStart); |
| assert tree.getCondition() != null; |
| // Determine whether the loop condition has the constant value true, according to the |
| // compiler logic. |
| boolean isCondConstTrue = TreeUtils.isExprConstTrue(tree.getCondition()); |
| |
| unbox(scan(tree.getCondition(), p)); |
| |
| if (!isCondConstTrue) { |
| // If the loop condition does not have the constant value true, the control flow is |
| // split into two branches. |
| ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); |
| extendWithExtendedNode(cjump); |
| } |
| |
| // Loop body |
| addLabelForNextNode(loopEntry); |
| assert tree.getStatement() != null; |
| scan(tree.getStatement(), p); |
| |
| if (isCondConstTrue) { |
| // The condition has the constant value true, so we can directly jump back to the loop entry. |
| extendWithExtendedNode(new UnconditionalJump(loopEntry)); |
| } else { |
| // Otherwise, jump back to evaluate the condition. |
| 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) { |
| throw new BugInCF("WildcardTree is unexpected in AST to CFG translation"); |
| } |
| |
| @Override |
| public Node visitOther(Tree tree, Void p) { |
| throw new BugInCF("Unknown AST element encountered in AST to CFG translation."); |
| } |
| |
| /** |
| * Returns the TypeMirror for the given class. |
| * |
| * @param clazz a class |
| * @return the TypeMirror for the class |
| */ |
| private TypeMirror getTypeMirror(Class<?> clazz) { |
| return TypesUtils.typeFromClass(clazz, types, elements); |
| } |
| |
| /** |
| * Returns the TypeMirror for the given class, or {@code null} if the type is not present. |
| * |
| * <p>This can be used to handle system types that are not present. For example, in Java code that |
| * is translated to JavaScript using j2cl, the custom bootclasspath contains APIs that are |
| * emulated in JavaScript, so some types such as OutOfMemoryError are deliberately not present. |
| * |
| * @param clazz a class, which must have a canonical name |
| * @return the TypeMirror for the class, or {@code null} if the type is not present |
| */ |
| private @Nullable TypeMirror maybeGetTypeMirror(Class<?> clazz) { |
| String name = clazz.getCanonicalName(); |
| assert name != null : clazz + " does not have a canonical name"; |
| TypeElement element = elements.getTypeElement(name); |
| if (element == null) { |
| return null; |
| } |
| return element.asType(); |
| } |
| } |