package org.checkerframework.dataflow.cfg.builder;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.BreakTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ContinueTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.EmptyStatementTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ErroneousTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.PrimitiveTypeTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.UnionTypeTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.tree.WildcardTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Type;
import org.checkerframework.checker.interning.qual.FindDistinct;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.Store.FlowRule;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
import org.checkerframework.dataflow.cfg.node.ArrayTypeNode;
import org.checkerframework.dataflow.cfg.node.AssertionErrorNode;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.BitwiseAndNode;
import org.checkerframework.dataflow.cfg.node.BitwiseComplementNode;
import org.checkerframework.dataflow.cfg.node.BitwiseOrNode;
import org.checkerframework.dataflow.cfg.node.BitwiseXorNode;
import org.checkerframework.dataflow.cfg.node.BooleanLiteralNode;
import org.checkerframework.dataflow.cfg.node.CaseNode;
import org.checkerframework.dataflow.cfg.node.CatchMarkerNode;
import org.checkerframework.dataflow.cfg.node.CharacterLiteralNode;
import org.checkerframework.dataflow.cfg.node.ClassDeclarationNode;
import org.checkerframework.dataflow.cfg.node.ClassNameNode;
import org.checkerframework.dataflow.cfg.node.ConditionalAndNode;
import org.checkerframework.dataflow.cfg.node.ConditionalNotNode;
import org.checkerframework.dataflow.cfg.node.ConditionalOrNode;
import org.checkerframework.dataflow.cfg.node.DeconstructorPatternNode;
import org.checkerframework.dataflow.cfg.node.DoubleLiteralNode;
import org.checkerframework.dataflow.cfg.node.EqualToNode;
import org.checkerframework.dataflow.cfg.node.ExplicitThisNode;
import org.checkerframework.dataflow.cfg.node.ExpressionStatementNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.FloatLiteralNode;
import org.checkerframework.dataflow.cfg.node.FloatingDivisionNode;
import org.checkerframework.dataflow.cfg.node.FloatingRemainderNode;
import org.checkerframework.dataflow.cfg.node.FunctionalInterfaceNode;
import org.checkerframework.dataflow.cfg.node.GreaterThanNode;
import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode;
import org.checkerframework.dataflow.cfg.node.ImplicitThisNode;
import org.checkerframework.dataflow.cfg.node.InstanceOfNode;
import org.checkerframework.dataflow.cfg.node.IntegerDivisionNode;
import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode;
import org.checkerframework.dataflow.cfg.node.IntegerRemainderNode;
import org.checkerframework.dataflow.cfg.node.LambdaResultExpressionNode;
import org.checkerframework.dataflow.cfg.node.LeftShiftNode;
import org.checkerframework.dataflow.cfg.node.LessThanNode;
import org.checkerframework.dataflow.cfg.node.LessThanOrEqualNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.LongLiteralNode;
import org.checkerframework.dataflow.cfg.node.MarkerNode;
import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.NarrowingConversionNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.NotEqualNode;
import org.checkerframework.dataflow.cfg.node.NullChkNode;
import org.checkerframework.dataflow.cfg.node.NullLiteralNode;
import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
import org.checkerframework.dataflow.cfg.node.NumericalMinusNode;
import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
import org.checkerframework.dataflow.cfg.node.NumericalPlusNode;
import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
import org.checkerframework.dataflow.cfg.node.PackageNameNode;
import org.checkerframework.dataflow.cfg.node.ParameterizedTypeNode;
import org.checkerframework.dataflow.cfg.node.PrimitiveTypeNode;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
import org.checkerframework.dataflow.cfg.node.SignedRightShiftNode;
import org.checkerframework.dataflow.cfg.node.StringConcatenateNode;
import org.checkerframework.dataflow.cfg.node.StringConversionNode;
import org.checkerframework.dataflow.cfg.node.StringLiteralNode;
import org.checkerframework.dataflow.cfg.node.SuperNode;
import org.checkerframework.dataflow.cfg.node.SwitchExpressionNode;
import org.checkerframework.dataflow.cfg.node.SynchronizedNode;
import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
import org.checkerframework.dataflow.cfg.node.ThisNode;
import org.checkerframework.dataflow.cfg.node.ThrowNode;
import org.checkerframework.dataflow.cfg.node.TypeCastNode;
import org.checkerframework.dataflow.cfg.node.UnsignedRightShiftNode;
import org.checkerframework.dataflow.cfg.node.ValueLiteralNode;
import org.checkerframework.dataflow.cfg.node.VariableDeclarationNode;
import org.checkerframework.dataflow.cfg.node.WideningConversionNode;
import org.checkerframework.dataflow.qual.AssertMethod;
import org.checkerframework.dataflow.qual.TerminatesExecution;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.SystemUtil;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.BindingPatternUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.CaseUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.DeconstructionPatternUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.InstanceOfUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.SwitchExpressionUtils;
import org.checkerframework.javacutil.TreeUtilsAfterJava11.YieldUtils;
import org.checkerframework.javacutil.TypeAnnotationUtils;
import org.checkerframework.javacutil.TypeKindUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.trees.TreeBuilder;
import org.plumelib.util.ArrayMap;
import org.plumelib.util.ArraySet;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.IPair;
import org.plumelib.util.IdentityArraySet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.ReferenceType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/**
* Class that performs phase one of the translation process. It generates the following information:
*
*
* A sequence of extended nodes.
* A set of bindings from {@link Label}s to positions in the node sequence.
* A set of leader nodes that give rise to basic blocks in phase two.
* A mapping from AST tree nodes to {@link Node}s.
*
*
* The return type of this scanner is {@link Node}. For expressions, the corresponding node is
* returned to allow linking between different nodes.
*
*
However, for statements there is usually no single {@link Node} that is created, and thus null
* is returned.
*
*
Every {@code visit*} method is assumed to add at least one extended node to the list of nodes
* (which might only be a jump).
*
*
The entry point to process a single body (e.g., method, lambda, top-level block) is {@link
* #process(TreePath, UnderlyingAST)}.
*/
@SuppressWarnings("nullness") // TODO
public class CFGTranslationPhaseOne extends TreeScanner {
/** Path to the tree currently being scanned. */
private TreePath path;
/** Annotation processing environment. */
protected final ProcessingEnvironment env;
/** Element utilities. */
protected final Elements elements;
/** Type utilities. */
protected final Types types;
/** Tree utilities. */
protected final Trees trees;
/** TreeBuilder instance. */
protected final TreeBuilder treeBuilder;
/** The annotation provider, e.g., a type factory. */
protected final AnnotationProvider annotationProvider;
/** Can assertions be assumed to be disabled? */
protected final boolean assumeAssertionsDisabled;
/** Can assertions be assumed to be enabled? */
protected final boolean assumeAssertionsEnabled;
/* --------------------------------------------------------- */
/* Extended Node Types and Labels */
/* --------------------------------------------------------- */
/** Special label to identify the regular exit. */
private final Label regularExitLabel;
/** Special label to identify the exceptional exit. */
private final Label exceptionalExitLabel;
/**
* Current {@link LabelCell} to which a return statement should jump, or null if there is no
* valid destination.
*/
private @Nullable LabelCell returnTargetLC;
/**
* Current {@link LabelCell} to which a break statement with no label should jump, or null if
* there is no valid destination.
*/
private @Nullable LabelCell breakTargetLC;
/**
* Map from AST label Names to CFG {@link Label}s for breaks. Each labeled statement creates two
* CFG {@link Label}s, one for break and one for continue.
*/
private Map breakLabels;
/**
* Current {@link LabelCell} to which a continue statement with no label should jump, or null if
* there is no valid destination.
*/
private @Nullable LabelCell continueTargetLC;
/**
* Map from AST label Names to CFG {@link Label}s for continues. Each labeled statement creates
* two CFG {@link Label}s, one for break and one for continue.
*/
private Map continueLabels;
/** Nested scopes of try-catch blocks in force at the current program point. */
private final TryStack tryStack;
/**
* SwitchBuilder for the current switch. Used to match yield statements to enclosing switches.
*/
private SwitchBuilder switchBuilder;
/**
* Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will
* have at least one corresponding Node. Trees that undergo conversions, such as boxing or
* unboxing, can map to two distinct Nodes. The Node for the pre-conversion value is stored in
* the treeToCfgNodes, while the Node for the post-conversion value is stored in the
* treeToConvertedCfgNodes.
*/
private final IdentityHashMap> treeToCfgNodes;
/** Map from AST {@link Tree}s to post-conversion sets of {@link Node}s. */
private final IdentityHashMap> treeToConvertedCfgNodes;
/**
* Map from postfix increment or decrement trees that are AST {@link UnaryTree}s to the
* synthetic tree that is {@code v + 1} or {@code v - 1}.
*/
private final IdentityHashMap postfixTreeToCfgNodes;
/** The list of extended nodes. */
private final ArrayList nodeList;
/** The bindings of labels to positions (i.e., indices) in the {@code nodeList}. */
private final Map bindings;
/** The set of leaders (represented as indices into {@code nodeList}). */
private final Set leaders;
/**
* All return nodes (if any) encountered. Only includes return statements that actually return
* something.
*/
private final List returnNodes;
/**
* Class declarations that have been encountered when building the control-flow graph for a
* method.
*/
private final List declaredClasses;
/**
* Lambdas encountered when building the control-flow graph for a method, variable initializer,
* or initializer.
*/
private final List declaredLambdas;
/** The ArithmeticException type. */
protected final TypeMirror arithmeticExceptionType;
/** The ArrayIndexOutOfBoundsException type. */
protected final TypeMirror arrayIndexOutOfBoundsExceptionType;
/** The AssertionError type. */
protected final TypeMirror assertionErrorType;
/** The ClassCastException type . */
protected final TypeMirror classCastExceptionType;
/** The Iterable type (erased). */
protected final TypeMirror iterableType;
/** The NegativeArraySizeException type. */
protected final TypeMirror negativeArraySizeExceptionType;
/** The NullPointerException type . */
protected final TypeMirror nullPointerExceptionType;
/** The OutOfMemoryError type. */
protected final @Nullable TypeMirror outOfMemoryErrorType;
/** The ClassCircularityError type. */
protected final @Nullable TypeMirror classCircularityErrorType;
/** The ClassFormatErrorType type. */
protected final @Nullable TypeMirror classFormatErrorType;
/** The NoClassDefFoundError type. */
protected final @Nullable TypeMirror noClassDefFoundErrorType;
/** The String type. */
protected final TypeMirror stringType;
/** The Throwable type. */
protected final TypeMirror throwableType;
/**
* Supertypes of all unchecked exceptions. The size is 2 and the contents are {@code
* RuntimeException} and {@code Error}.
*/
protected final Set uncheckedExceptionTypes;
/**
* Exceptions that can be thrown by array creation "new SomeType[]". The size is 2 and the
* contents are {@code NegativeArraySizeException} and {@code OutOfMemoryError}. This list comes
* from JLS 15.10.1 "Run-Time Evaluation of Array Creation Expressions".
*/
protected final Set newArrayExceptionTypes;
/**
* Creates {@link CFGTranslationPhaseOne}.
*
* @param treeBuilder builder for new AST nodes
* @param annotationProvider extracts annotations from AST nodes
* @param assumeAssertionsDisabled can assertions be assumed to be disabled?
* @param assumeAssertionsEnabled can assertions be assumed to be enabled?
* @param env annotation processing environment containing type utilities
*/
public CFGTranslationPhaseOne(
TreeBuilder treeBuilder,
AnnotationProvider annotationProvider,
boolean assumeAssertionsEnabled,
boolean assumeAssertionsDisabled,
ProcessingEnvironment env) {
this.env = env;
this.treeBuilder = treeBuilder;
this.annotationProvider = annotationProvider;
assert !(assumeAssertionsDisabled && assumeAssertionsEnabled);
this.assumeAssertionsEnabled = assumeAssertionsEnabled;
this.assumeAssertionsDisabled = assumeAssertionsDisabled;
elements = env.getElementUtils();
types = env.getTypeUtils();
trees = Trees.instance(env);
// initialize lists and maps
treeToCfgNodes = new IdentityHashMap<>();
treeToConvertedCfgNodes = new IdentityHashMap<>();
postfixTreeToCfgNodes = new IdentityHashMap<>();
nodeList = new ArrayList<>();
bindings = new HashMap<>();
leaders = new HashSet<>();
regularExitLabel = new Label();
exceptionalExitLabel = new Label();
tryStack = new TryStack(exceptionalExitLabel);
returnTargetLC = new LabelCell(regularExitLabel);
breakLabels = new HashMap<>(2);
continueLabels = new HashMap<>(2);
returnNodes = new ArrayList<>();
declaredClasses = new ArrayList<>();
declaredLambdas = new ArrayList<>();
arithmeticExceptionType = getTypeMirror(ArithmeticException.class);
arrayIndexOutOfBoundsExceptionType = getTypeMirror(ArrayIndexOutOfBoundsException.class);
assertionErrorType = getTypeMirror(AssertionError.class);
classCastExceptionType = getTypeMirror(ClassCastException.class);
iterableType = types.erasure(getTypeMirror(Iterable.class));
negativeArraySizeExceptionType = getTypeMirror(NegativeArraySizeException.class);
nullPointerExceptionType = getTypeMirror(NullPointerException.class);
outOfMemoryErrorType = maybeGetTypeMirror(OutOfMemoryError.class);
classCircularityErrorType = maybeGetTypeMirror(ClassCircularityError.class);
classFormatErrorType = maybeGetTypeMirror(ClassFormatError.class);
noClassDefFoundErrorType = maybeGetTypeMirror(NoClassDefFoundError.class);
stringType = getTypeMirror(String.class);
throwableType = getTypeMirror(Throwable.class);
uncheckedExceptionTypes = new ArraySet<>(2);
uncheckedExceptionTypes.add(getTypeMirror(RuntimeException.class));
uncheckedExceptionTypes.add(getTypeMirror(Error.class));
newArrayExceptionTypes = new ArraySet<>(2);
newArrayExceptionTypes.add(negativeArraySizeExceptionType);
if (outOfMemoryErrorType != null) {
newArrayExceptionTypes.add(outOfMemoryErrorType);
}
}
/**
* Performs the actual work of phase one: processing a single body (of a method, lambda,
* top-level block, etc.).
*
* @param bodyPath path to the body of the underlying AST's method
* @param underlyingAST the AST for which the CFG is to be built
* @return the result of phase one
*/
public PhaseOneResult process(TreePath bodyPath, UnderlyingAST underlyingAST) {
// Set class variables
this.path = bodyPath;
// Traverse AST of the method body.
try { // "finally" clause is "this.path = null"
Node finalNode = scan(path.getLeaf(), null);
// If we are building the CFG for a lambda with a single expression as the body, then
// add an extra node for the result of that lambda.
if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) {
LambdaExpressionTree lambdaTree =
((UnderlyingAST.CFGLambda) underlyingAST).getLambdaTree();
if (lambdaTree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION) {
Node resultNode =
new LambdaResultExpressionNode(
(ExpressionTree) lambdaTree.getBody(), finalNode);
extendWithNode(resultNode);
}
}
// Add marker to indicate that the next block will be the exit block.
// Note: if there is a return statement earlier in the method (which is always the case
// for non-void methods), then this is not strictly necessary. However, it is also not a
// problem, as it will just generate a degenerate control graph case that will be
// removed in a later phase.
nodeList.add(new UnconditionalJump(regularExitLabel));
return new PhaseOneResult(
underlyingAST,
treeToCfgNodes,
treeToConvertedCfgNodes,
postfixTreeToCfgNodes,
nodeList,
bindings,
leaders,
returnNodes,
regularExitLabel,
exceptionalExitLabel,
declaredClasses,
declaredLambdas,
types);
} finally {
this.path = null;
}
}
/**
* Process a single body within {@code root}. This method does not process the entire given
* CompilationUnitTree. Rather, it processes one body (of a method/lambda/etc.) within it, which
* corresponds to {@code underlyingAST}.
*
* @param root the compilation unit
* @param underlyingAST the AST corresponding to the body to process
* @return a PhaseOneResult
*/
public PhaseOneResult process(CompilationUnitTree root, UnderlyingAST underlyingAST) {
// TODO: Isn't this costly? Is there no cache we can reuse?
TreePath bodyPath = trees.getPath(root, underlyingAST.getCode());
assert bodyPath != null;
return process(bodyPath, underlyingAST);
}
/**
* Perform any actions required when CFG translation creates a new Tree that is not part of the
* original AST.
*
* @param tree the newly created Tree
*/
public void handleArtificialTree(Tree tree) {}
/**
* Returns the current path for the tree currently being scanned.
*
* @return the current path
*/
public TreePath getCurrentPath() {
return path;
}
// TODO: remove method and instead use JCP to add version-specific methods.
// Switch expressions first appeared in 12, standard in 14, so don't use 17.
// TODO: look into changing back to TreePathScanner and removing path/getCurrentPath.
@Override
public Node scan(Tree tree, Void p) {
if (tree == null) {
return null;
}
TreePath prev = path;
@SuppressWarnings("interning:not.interned") // Looking for exact match.
boolean treeIsLeaf = path.getLeaf() != tree;
if (treeIsLeaf) {
path = new TreePath(path, tree);
}
try {
// TODO: use JCP to add version-specific behavior
if (SystemUtil.jreVersion >= 14) {
// Must use String comparison to support compiling on JDK 11 and earlier.
// Features added between JDK 12 and JDK 17 inclusive.
switch (tree.getKind().name()) {
case "BINDING_PATTERN":
return visitBindingPattern17(path.getLeaf(), p);
case "SWITCH_EXPRESSION":
return visitSwitchExpression17(tree, p);
case "YIELD":
return visitYield17(tree, p);
case "DECONSTRUCTION_PATTERN":
return visitDeconstructionPattern21(tree, p);
default:
// fall through to generic behavior
}
}
return tree.accept(this, p);
} finally {
path = prev;
}
}
/**
* Visit a SwitchExpressionTree.
*
* @param yieldTree a YieldTree, typed as Tree to be backward-compatible
* @param p parameter
* @return the result of visiting the switch expression tree
*/
public Node visitYield17(Tree yieldTree, Void p) {
ExpressionTree resultExpression = YieldUtils.getValue(yieldTree);
switchBuilder.buildSwitchExpressionResult(resultExpression);
return null;
}
/**
* Visit a SwitchExpressionTree
*
* @param switchExpressionTree a SwitchExpressionTree, typed as Tree to be backward-compatible
* @param p parameter
* @return the result of visiting the switch expression tree
*/
public Node visitSwitchExpression17(Tree switchExpressionTree, Void p) {
SwitchBuilder oldSwitchBuilder = switchBuilder;
switchBuilder = new SwitchBuilder(switchExpressionTree);
Node result = switchBuilder.build();
switchBuilder = oldSwitchBuilder;
return result;
}
/**
* Visit a BindingPatternTree
*
* @param bindingPatternTree a BindingPatternTree, typed as Tree to be backward-compatible
* @param p parameter
* @return the result of visiting the binding pattern tree
*/
public Node visitBindingPattern17(Tree bindingPatternTree, Void p) {
ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath());
TypeElement classElem = TreeUtils.elementFromDeclaration(enclosingClass);
Node receiver = new ImplicitThisNode(classElem.asType());
VariableTree varTree = BindingPatternUtils.getVariable(bindingPatternTree);
VariableDeclarationNode variableDeclarationNode = new VariableDeclarationNode(varTree);
extendWithNode(variableDeclarationNode);
LocalVariableNode varNode = new LocalVariableNode(varTree, receiver);
extendWithNode(varNode);
return varNode;
}
/**
* Visit a DeconstructionPatternTree.
*
* @param deconstructionPatternTree a DeconstructionPatternTree, typed as Tree so the Checker
* Framework compiles under JDK 20 and earlier
* @param p an unused parameter
* @return the result of visiting the tree
*/
public Node visitDeconstructionPattern21(Tree deconstructionPatternTree, Void p) {
List extends Tree> nestedPatternTrees =
DeconstructionPatternUtils.getNestedPatterns(deconstructionPatternTree);
List nestedPatterns = new ArrayList<>(nestedPatternTrees.size());
for (Tree pattern : nestedPatternTrees) {
nestedPatterns.add(scan(pattern, p));
}
DeconstructorPatternNode dcpN =
new DeconstructorPatternNode(
TreeUtils.typeOf(deconstructionPatternTree),
deconstructionPatternTree,
nestedPatterns);
extendWithNode(dcpN);
return dcpN;
}
/* --------------------------------------------------------- */
/* Nodes and Labels Management */
/* --------------------------------------------------------- */
/**
* Add a node to the lookup map if it not already present.
*
* @param node the node to add to the lookup map
*/
protected void addToLookupMap(Node node) {
Tree tree = node.getTree();
if (tree == null) {
return;
}
Set existing = treeToCfgNodes.get(tree);
if (existing == null) {
Set newSet = new IdentityArraySet(1);
newSet.add(node);
treeToCfgNodes.put(tree, newSet);
} else {
existing.add(node);
}
Tree enclosingParens = parenMapping.get(tree);
while (enclosingParens != null) {
Set exp =
treeToCfgNodes.computeIfAbsent(enclosingParens, k -> new IdentityArraySet<>(1));
// `node` could already be in set `exp`, but it's probably as fast to just add again
exp.add(node);
enclosingParens = parenMapping.get(enclosingParens);
}
}
/**
* Add a node in the post-conversion lookup map. The node should refer to a Tree and that Tree
* should already be in the pre-conversion lookup map. This method is used to update the
* Tree-Node mapping with conversion nodes.
*
* @param node the node to add to the lookup map
*/
protected void addToConvertedLookupMap(Node node) {
Tree tree = node.getTree();
addToConvertedLookupMap(tree, node);
}
/**
* Add a node in the post-conversion lookup map. The tree argument should already be in the
* pre-conversion lookup map. This method is used to update the Tree-Node mapping with
* conversion nodes.
*
* @param tree the tree used as a key in the map
* @param node the node to add to the lookup map
*/
protected void addToConvertedLookupMap(Tree tree, Node node) {
assert tree != null;
assert treeToCfgNodes.containsKey(tree);
Set existing = treeToConvertedCfgNodes.get(tree);
if (existing == null) {
Set newSet = new IdentityArraySet<>(1);
newSet.add(node);
treeToConvertedCfgNodes.put(tree, newSet);
} else {
existing.add(node);
}
}
/**
* Extend the list of extended nodes with a node.
*
* @param node the node to add
*/
protected void extendWithNode(Node node) {
addToLookupMap(node);
extendWithExtendedNode(new NodeHolder(node));
}
/**
* Extend the list of extended nodes with a node, where {@code node} might throw the exception
* {@code cause}.
*
* @param node the node to add
* @param cause an exception that the node might throw
* @return the node holder
*/
protected NodeWithExceptionsHolder extendWithNodeWithException(Node node, TypeMirror cause) {
addToLookupMap(node);
return extendWithNodeWithExceptions(node, Collections.singleton(cause));
}
/**
* Extend the list of extended nodes with a node, where {@code node} might throw any of the
* exceptions in {@code causes}.
*
* @param node the node to add
* @param causes set of exceptions that the node might throw
* @return the node holder
*/
protected NodeWithExceptionsHolder extendWithNodeWithExceptions(
Node node, Set causes) {
addToLookupMap(node);
Map> exceptions = new ArrayMap<>(causes.size());
for (TypeMirror cause : causes) {
exceptions.put(cause, tryStack.possibleLabels(cause));
}
NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions);
extendWithExtendedNode(exNode);
return exNode;
}
/**
* Extend a list of extended nodes with a ClassName node.
*
* Evaluating a class literal kicks off class loading (JLS 15.8.2) which can fail and throw
* one of the specified subclasses of a LinkageError or an OutOfMemoryError (JLS 12.2.1).
*
* @param node the ClassName node to add
* @return the node holder
*/
protected NodeWithExceptionsHolder extendWithClassNameNode(ClassNameNode node) {
Set thrownSet = new ArraySet<>(4);
if (classCircularityErrorType != null) {
thrownSet.add(classCircularityErrorType);
}
if (classFormatErrorType != null) {
thrownSet.add(classFormatErrorType);
}
if (noClassDefFoundErrorType != null) {
thrownSet.add(noClassDefFoundErrorType);
}
if (outOfMemoryErrorType != null) {
thrownSet.add(outOfMemoryErrorType);
}
return extendWithNodeWithExceptions(node, thrownSet);
}
/**
* Insert {@code node} after {@code pred} in the list of extended nodes, or append to the list
* if {@code pred} is not present.
*
* @param node the node to add
* @param pred the desired predecessor of node
* @return the node holder
*/
protected T insertNodeAfter(T node, Node pred) {
addToLookupMap(node);
insertExtendedNodeAfter(new NodeHolder(node), pred);
return node;
}
/**
* Insert a {@code node} that might throw the exceptions in {@code causes} after {@code pred} in
* the list of extended nodes, or append to the list if {@code pred} is not present.
*
* @param node the node to add
* @param causes set of exceptions that the node might throw
* @param pred the desired predecessor of node
* @return the node holder
*/
protected NodeWithExceptionsHolder insertNodeWithExceptionsAfter(
Node node, Set causes, Node pred) {
addToLookupMap(node);
Map> exceptions = new ArrayMap<>(causes.size());
for (TypeMirror cause : causes) {
exceptions.put(cause, tryStack.possibleLabels(cause));
}
NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder(node, exceptions);
insertExtendedNodeAfter(exNode, pred);
return exNode;
}
/**
* Extend the list of extended nodes with an extended node.
*
* @param n the extended node
*/
protected void extendWithExtendedNode(ExtendedNode n) {
nodeList.add(n);
}
/**
* Insert {@code n} after the node {@code pred} in the list of extended nodes, or append {@code
* n} if {@code pred} is not present.
*
* @param n the extended node
* @param pred the desired predecessor
*/
@SuppressWarnings("ModifyCollectionInEnhancedForLoop")
protected void insertExtendedNodeAfter(ExtendedNode n, @FindDistinct Node pred) {
int index = -1;
for (int i = 0; i < nodeList.size(); i++) {
ExtendedNode inList = nodeList.get(i);
if (inList instanceof NodeHolder || inList instanceof NodeWithExceptionsHolder) {
if (inList.getNode() == pred) {
index = i;
break;
}
}
}
if (index != -1) {
nodeList.add(index + 1, n);
// update bindings
for (Map.Entry e : bindings.entrySet()) {
if (e.getValue() >= index + 1) {
bindings.put(e.getKey(), e.getValue() + 1);
}
}
// update leaders
Set oldLeaders = new HashSet<>(leaders);
leaders.clear();
for (Integer l : oldLeaders) {
if (l >= index + 1) {
leaders.add(l + 1);
} else {
leaders.add(l);
}
}
} else {
nodeList.add(n);
}
}
/**
* Add the label {@code l} to the extended node that will be placed next in the sequence.
*
* @param l the node to add to the forthcoming extended node
*/
protected void addLabelForNextNode(Label l) {
if (bindings.containsKey(l)) {
throw new BugInCF("bindings already contains key %s: %s", l, bindings);
}
leaders.add(nodeList.size());
bindings.put(l, nodeList.size());
}
/* --------------------------------------------------------- */
/* Utility Methods */
/* --------------------------------------------------------- */
/** The UID for the next unique name. */
protected long uid = 0;
/**
* Returns a unique name starting with {@code prefix}.
*
* @param prefix the prefix of the unique name
* @return a unique name starting with {@code prefix}
*/
protected String uniqueName(String prefix) {
return prefix + "#num" + uid++;
}
/**
* If the input node is an unboxed primitive type, insert a call to the appropriate valueOf
* method, otherwise leave it alone.
*
* @param node in input node
* @return a Node representing the boxed version of the input, which may simply be the input
* node
*/
protected Node box(Node node) {
// For boxing conversion, see JLS 5.1.7
if (TypesUtils.isPrimitive(node.getType())) {
PrimitiveType primitive = types.getPrimitiveType(node.getType().getKind());
TypeMirror boxedType = types.getDeclaredType(types.boxedClass(primitive));
TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement();
IdentifierTree classTree = treeBuilder.buildClassUse(boxedElement);
handleArtificialTree(classTree);
// No need to handle possible errors from evaluating a class literal here
// since this is synthetic code that can't fail.
ClassNameNode className = new ClassNameNode(classTree);
className.setInSource(false);
insertNodeAfter(className, node);
MemberSelectTree valueOfSelect = treeBuilder.buildValueOfMethodAccess(classTree);
handleArtificialTree(valueOfSelect);
MethodAccessNode valueOfAccess = new MethodAccessNode(valueOfSelect, className);
valueOfAccess.setInSource(false);
insertNodeAfter(valueOfAccess, className);
MethodInvocationTree valueOfCall =
treeBuilder.buildMethodInvocation(
valueOfSelect, (ExpressionTree) node.getTree());
handleArtificialTree(valueOfCall);
Node boxed =
new MethodInvocationNode(
valueOfCall,
valueOfAccess,
Collections.singletonList(node),
getCurrentPath());
boxed.setInSource(false);
// Add Throwable to account for unchecked exceptions
addToConvertedLookupMap(node.getTree(), boxed);
insertNodeWithExceptionsAfter(boxed, uncheckedExceptionTypes, valueOfAccess);
return boxed;
} else {
return node;
}
}
/**
* If the input node is a boxed type, unbox it, otherwise leave it alone.
*
* @param node in input node
* @return a Node representing the unboxed version of the input, which may simply be the input
* node
*/
protected Node unbox(Node node) {
if (TypesUtils.isBoxedPrimitive(node.getType())) {
MemberSelectTree primValueSelect =
treeBuilder.buildPrimValueMethodAccess(node.getTree());
handleArtificialTree(primValueSelect);
MethodAccessNode primValueAccess = new MethodAccessNode(primValueSelect, node);
primValueAccess.setInSource(false);
// Method access may throw NullPointerException
insertNodeWithExceptionsAfter(
primValueAccess, Collections.singleton(nullPointerExceptionType), node);
MethodInvocationTree primValueCall = treeBuilder.buildMethodInvocation(primValueSelect);
handleArtificialTree(primValueCall);
Node unboxed =
new MethodInvocationNode(
primValueCall,
primValueAccess,
Collections.emptyList(),
getCurrentPath());
unboxed.setInSource(false);
// Add Throwable to account for unchecked exceptions
addToConvertedLookupMap(node.getTree(), unboxed);
insertNodeWithExceptionsAfter(unboxed, uncheckedExceptionTypes, primValueAccess);
return unboxed;
} else {
return node;
}
}
private TreeInfo getTreeInfo(Tree tree) {
TypeMirror type = TreeUtils.typeOf(tree);
boolean boxed = TypesUtils.isBoxedPrimitive(type);
TypeMirror unboxedType = boxed ? types.unboxedType(type) : type;
boolean bool = TypesUtils.isBooleanType(type);
boolean numeric = TypesUtils.isNumeric(unboxedType);
return new TreeInfo() {
@Override
public boolean isNumeric() {
return numeric;
}
@Override
public boolean isBoxed() {
return boxed;
}
@Override
public boolean isBoolean() {
return bool;
}
@Override
public TypeMirror unboxedType() {
return unboxedType;
}
};
}
/**
* Returns the unboxed tree if necessary, as described in JLS 5.1.8.
*
* @return the unboxed tree if necessary, as described in JLS 5.1.8
*/
private Node unboxAsNeeded(Node node, boolean boxed) {
return boxed ? unbox(node) : node;
}
/**
* Convert the input node to String type, if it isn't already.
*
* @param node an input node
* @return a Node with the value promoted to String, which may be the input node
*/
protected Node stringConversion(Node node) {
// For string conversion, see JLS 5.1.11
if (!TypesUtils.isString(node.getType())) {
Node converted = new StringConversionNode(node.getTree(), node, stringType);
addToConvertedLookupMap(converted);
insertNodeAfter(converted, node);
return converted;
} else {
return node;
}
}
/**
* Perform unary numeric promotion on the input node.
*
* @param node a node producing a value of numeric primitive or boxed type
* @return a Node with the value promoted to the int, long, float, or double; may return be the
* input node
*/
protected Node unaryNumericPromotion(Node node) {
// For unary numeric promotion, see JLS 5.6.1
node = unbox(node);
switch (node.getType().getKind()) {
case BYTE:
case CHAR:
case SHORT:
{
TypeMirror intType = types.getPrimitiveType(TypeKind.INT);
Node widened = new WideningConversionNode(node.getTree(), node, intType);
addToConvertedLookupMap(widened);
insertNodeAfter(widened, node);
return widened;
}
default:
// Nothing to do.
break;
}
return node;
}
/**
* Returns true if the argument type is a numeric primitive or a boxed numeric primitive and
* false otherwise.
*/
protected boolean isNumericOrBoxed(TypeMirror type) {
if (TypesUtils.isBoxedPrimitive(type)) {
type = types.unboxedType(type);
}
return TypesUtils.isNumeric(type);
}
/**
* Compute the type to which two numeric types must be promoted before performing a binary
* numeric operation on them. The input types must both be numeric and the output type is
* primitive.
*
* @param left the type of the left operand
* @param right the type of the right operand
* @return a TypeMirror representing the binary numeric promoted type
*/
protected TypeMirror binaryPromotedType(TypeMirror left, TypeMirror right) {
if (TypesUtils.isBoxedPrimitive(left)) {
left = types.unboxedType(left);
}
if (TypesUtils.isBoxedPrimitive(right)) {
right = types.unboxedType(right);
}
TypeKind promotedTypeKind = TypeKindUtils.widenedNumericType(left, right);
return types.getPrimitiveType(promotedTypeKind);
}
/**
* Perform binary numeric promotion on the input node to make it match the expression type.
*
* @param node a node producing a value of numeric primitive or boxed type
* @param exprType the type to promote the value to
* @return a Node with the value promoted to the exprType, which may be the input node
*/
protected Node binaryNumericPromotion(Node node, TypeMirror exprType) {
// For binary numeric promotion, see JLS 5.6.2
node = unbox(node);
if (!types.isSameType(node.getType(), exprType)) {
Node widened = new WideningConversionNode(node.getTree(), node, exprType);
addToConvertedLookupMap(widened);
insertNodeAfter(widened, node);
return widened;
} else {
return node;
}
}
/**
* Perform widening primitive conversion on the input node to make it match the destination
* type.
*
* @param node a node producing a value of numeric primitive type
* @param destType the type to widen the value to
* @return a Node with the value widened to the exprType, which may be the input node
*/
protected Node widen(Node node, TypeMirror destType) {
// For widening conversion, see JLS 5.1.2
assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType)
: "widening must be applied to primitive types";
if (types.isSubtype(node.getType(), destType)
&& !types.isSameType(node.getType(), destType)) {
Node widened = new WideningConversionNode(node.getTree(), node, destType);
addToConvertedLookupMap(widened);
insertNodeAfter(widened, node);
return widened;
} else {
return node;
}
}
/**
* Perform narrowing conversion on the input node to make it match the destination type.
*
* @param node a node producing a value of numeric primitive type
* @param destType the type to narrow the value to
* @return a Node with the value narrowed to the exprType, which may be the input node
*/
protected Node narrow(Node node, TypeMirror destType) {
// For narrowing conversion, see JLS 5.1.3
assert TypesUtils.isPrimitive(node.getType()) && TypesUtils.isPrimitive(destType)
: "narrowing must be applied to primitive types";
if (types.isSubtype(destType, node.getType())
&& !types.isSameType(destType, node.getType())) {
Node narrowed = new NarrowingConversionNode(node.getTree(), node, destType);
addToConvertedLookupMap(narrowed);
insertNodeAfter(narrowed, node);
return narrowed;
} else {
return node;
}
}
/**
* Perform narrowing conversion and optionally boxing conversion on the input node to make it
* match the destination type.
*
* @param node a node producing a value of numeric primitive type
* @param destType the type to narrow the value to (possibly boxed)
* @return a Node with the value narrowed and boxed to the destType, which may be the input node
*/
protected Node narrowAndBox(Node node, TypeMirror destType) {
if (TypesUtils.isBoxedPrimitive(destType)) {
return box(narrow(node, types.unboxedType(destType)));
} else {
return narrow(node, destType);
}
}
/**
* Return whether a conversion from the type of the node to varType requires narrowing.
*
* @param varType the type of a variable (or general LHS) to be converted to
* @param node a node whose value is being converted
* @return whether this conversion requires narrowing to succeed
*/
protected boolean conversionRequiresNarrowing(TypeMirror varType, Node node) {
// Narrowing is restricted to cases where the left hand side is byte, char, short or Byte,
// Char, Short and the right hand side is a constant.
TypeMirror unboxedVarType =
TypesUtils.isBoxedPrimitive(varType) ? types.unboxedType(varType) : varType;
TypeKind unboxedVarKind = unboxedVarType.getKind();
boolean isLeftNarrowableTo =
unboxedVarKind == TypeKind.BYTE
|| unboxedVarKind == TypeKind.SHORT
|| unboxedVarKind == TypeKind.CHAR;
boolean isRightConstant = node instanceof ValueLiteralNode;
return isLeftNarrowableTo && isRightConstant;
}
/**
* Assignment conversion and method invocation conversion are almost identical, except that
* assignment conversion allows narrowing. We factor out the common logic here.
*
* @param node a Node producing a value
* @param varType the type of a variable
* @param contextAllowsNarrowing whether to allow narrowing (for assignment conversion) or not
* (for method invocation conversion)
* @return a Node with the value converted to the type of the variable, which may be the input
* node itself
*/
protected Node commonConvert(Node node, TypeMirror varType, boolean contextAllowsNarrowing) {
// For assignment conversion, see JLS 5.2
// For method invocation conversion, see JLS 5.3
// Check for identical types or "identity conversion"
TypeMirror nodeType = node.getType();
boolean isSameType = types.isSameType(nodeType, varType);
if (isSameType) {
return node;
}
boolean isRightNumeric = TypesUtils.isNumeric(nodeType);
boolean isRightPrimitive = TypesUtils.isPrimitive(nodeType);
boolean isRightBoxed = TypesUtils.isBoxedPrimitive(nodeType);
boolean isRightReference = nodeType instanceof ReferenceType;
boolean isLeftNumeric = TypesUtils.isNumeric(varType);
boolean isLeftPrimitive = TypesUtils.isPrimitive(varType);
// boolean isLeftBoxed = TypesUtils.isBoxedPrimitive(varType);
boolean isLeftReference = varType instanceof ReferenceType;
boolean isSubtype = types.isSubtype(nodeType, varType);
if (isRightNumeric && isLeftNumeric && isSubtype) {
node = widen(node, varType);
} else if (isRightReference && isLeftReference && isSubtype) {
// widening reference conversion is a no-op, but if it
// applies, then later conversions do not.
} else if (isRightPrimitive && isLeftReference) {
if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) {
node = narrowAndBox(node, varType);
} else {
node = box(node);
}
} else if (isRightBoxed && isLeftPrimitive) {
node = unbox(node);
nodeType = node.getType();
if (types.isSubtype(nodeType, varType) && !types.isSameType(nodeType, varType)) {
node = widen(node, varType);
}
} else if (isRightPrimitive && isLeftPrimitive) {
if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) {
node = narrow(node, varType);
}
}
// `node` might have been re-assigned; if `nodeType` is needed, set it again.
// nodeType = node.getType();
// TODO: if checkers need to know about null references of
// a particular type, add logic for them here.
return node;
}
/**
* Perform assignment conversion so that it can be assigned to a variable of the given type.
*
* @param node a Node producing a value
* @param varType the type of a variable
* @return a Node with the value converted to the type of the variable, which may be the input
* node itself
*/
protected Node assignConvert(Node node, TypeMirror varType) {
return commonConvert(node, varType, true);
}
/**
* Perform method invocation conversion so that the node can be passed as a formal parameter of
* the given type.
*
* @param node a Node producing a value
* @param formalType the type of a formal parameter
* @return a Node with the value converted to the type of the formal, which may be the input
* node itself
*/
protected Node methodInvocationConvert(Node node, TypeMirror formalType) {
return commonConvert(node, formalType, false);
}
/**
* Given a method element, its type at the call site, and a list of argument expressions, return
* a list of {@link Node}s representing the arguments converted for a call of the method. This
* method applies to both method invocations and constructor calls. The argument of newClassTree
* is null when we visit {@link MethodInvocationTree}, and is non-null when we visit {@link
* NewClassTree}.
*
* @param tree the invocation tree for the call
* @param executable an ExecutableElement representing a method/constructor to be called
* @param executableType an ExecutableType representing the type of the method/constructor call;
* the type must be viewpoint-adapted to the call
* @param actualExprs a List of argument expressions to a call
* @param newClassTree the NewClassTree if the method is the invocation of a constructor
* @return a List of {@link Node}s representing arguments after conversions required by a call
* to this method
*/
protected List convertCallArguments(
ExpressionTree tree,
ExecutableElement executable,
ExecutableType executableType,
List extends ExpressionTree> actualExprs,
@Nullable NewClassTree newClassTree) {
// NOTE: It is important to convert one method argument before generating CFG nodes for the
// next argument, since label binding expects nodes to be generated in execution order.
// Therefore, this method first determines which conversions need to be applied and then
// iterates over the actual arguments.
List extends TypeMirror> formals = executableType.getParameterTypes();
int numFormals = formals.size();
ArrayList convertedNodes = new ArrayList<>(numFormals);
AssertMethodTuple assertMethodTuple = getAssertMethodTuple(executable);
int numActuals = actualExprs.size();
if (executable.isVarArgs()) {
// Create a new array argument if the actuals outnumber the formals, or if the last
// actual is not assignable to the last formal.
int lastArgIndex = numFormals - 1;
TypeMirror lastParamType = formals.get(lastArgIndex);
if (numActuals == numFormals
&& types.isAssignable(
TreeUtils.typeOf(actualExprs.get(numActuals - 1)), lastParamType)) {
// Normal call with no array creation, apply method
// invocation conversion to all arguments.
for (int i = 0; i < numActuals; i++) {
Node actualVal = scan(actualExprs.get(i), null);
if (i == assertMethodTuple.booleanParam) {
treatMethodAsAssert(
(MethodInvocationTree) tree, assertMethodTuple, actualVal);
}
if (actualVal == null) {
throw new BugInCF(
"CFGBuilder: scan returned null for %s [%s]",
actualExprs.get(i), actualExprs.get(i).getClass());
}
convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i)));
}
} else {
assert lastParamType instanceof ArrayType
: "variable argument formal must be an array";
// Handle anonymous constructors with an explicit enclosing expression.
// There is a mismatch between the number of parameters and arguments
// when the following conditions are met:
// 1. Java version >= 11,
// 2. the method is an anonymous constructor,
// 3. the executable element has varargs,
// 4. the constructor is invoked with an explicit enclosing expression.
// In this case, the parameters have an enclosing expression as its first parameter,
// while the arguments do not have such element. Hence, decrease the lastArgIndex
// to organize the arguments from the correct index later.
if (SystemUtil.jreVersion >= 11
&& newClassTree != null
&& TreeUtils.isAnonymousConstructorWithExplicitEnclosingExpression(
executable, newClassTree)) {
lastArgIndex--;
}
// Apply method invocation conversion to lastArgIndex arguments and use the
// remaining ones to initialize an array.
for (int i = 0; i < lastArgIndex; i++) {
Node actualVal = scan(actualExprs.get(i), null);
if (i == assertMethodTuple.booleanParam) {
treatMethodAsAssert(
(MethodInvocationTree) tree, assertMethodTuple, actualVal);
}
convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i)));
}
TypeMirror elemType = ((ArrayType) lastParamType).getComponentType();
List inits = new ArrayList<>(numActuals - lastArgIndex);
List initializers = new ArrayList<>(numActuals - lastArgIndex);
for (int i = lastArgIndex; i < numActuals; i++) {
inits.add(actualExprs.get(i));
Node actualVal = scan(actualExprs.get(i), null);
initializers.add(assignConvert(actualVal, elemType));
}
NewArrayTree wrappedVarargs = treeBuilder.buildNewArray(elemType, inits);
handleArtificialTree(wrappedVarargs);
Node lastArgument =
new ArrayCreationNode(
wrappedVarargs,
lastParamType,
/* dimensions= */ Collections.emptyList(),
initializers);
extendWithNode(lastArgument);
convertedNodes.add(lastArgument);
}
} else {
for (int i = 0; i < numActuals; i++) {
Node actualVal = scan(actualExprs.get(i), null);
if (i == assertMethodTuple.booleanParam) {
treatMethodAsAssert((MethodInvocationTree) tree, assertMethodTuple, actualVal);
}
convertedNodes.add(methodInvocationConvert(actualVal, formals.get(i)));
}
}
return convertedNodes;
}
/**
* Returns the AssertMethodTuple for {@code method}. If {@code method} is not an assert method,
* then {@link AssertMethodTuple#NONE} is returned.
*
* @param method a method element that might be an assert method
* @return the AssertMethodTuple for {@code method}
*/
protected AssertMethodTuple getAssertMethodTuple(ExecutableElement method) {
AnnotationMirror assertMethodAnno =
annotationProvider.getDeclAnnotation(method, AssertMethod.class);
if (assertMethodAnno == null) {
return AssertMethodTuple.NONE;
}
// Dataflow does not require checker-qual.jar to be on the users classpath, so
// AnnotationUtils.getElementValue(...) cannot be used.
int booleanParam =
AnnotationUtils.getElementValueNotOnClasspath(
assertMethodAnno, "parameter", Integer.class, 1)
- 1;
TypeMirror exceptionType =
AnnotationUtils.getElementValueNotOnClasspath(
assertMethodAnno,
"value",
Type.ClassType.class,
(Type.ClassType) assertionErrorType);
boolean isAssertFalse =
AnnotationUtils.getElementValueNotOnClasspath(
assertMethodAnno, "isAssertFalse", Boolean.class, false);
return new AssertMethodTuple(booleanParam, exceptionType, isAssertFalse);
}
/** Holds the elements of an {@link AssertMethod} annotation. */
protected static class AssertMethodTuple {
/** A tuple representing the lack of an {@link AssertMethodTuple}. */
protected static final AssertMethodTuple NONE = new AssertMethodTuple(-1, null, false);
/**
* 0-based index of the parameter of the expression that is tested by the assert method. (Or
* -1 if this isn't an assert method.)
*/
public final int booleanParam;
/** The type of the exception thrown by the assert method. */
public final TypeMirror exceptionType;
/** Is this an assert false method? */
public final boolean isAssertFalse;
/**
* Creates an AssertMethodTuple.
*
* @param booleanParam 0-based index of the parameter of the expression that is tested by
* the assert method
* @param exceptionType the type of the exception thrown by the assert method
* @param isAssertFalse is this an assert false method
*/
public AssertMethodTuple(
int booleanParam, TypeMirror exceptionType, boolean isAssertFalse) {
this.booleanParam = booleanParam;
this.exceptionType = exceptionType;
this.isAssertFalse = isAssertFalse;
}
}
/**
* Convert an operand of a conditional expression to the type of the whole expression.
*
* @param node a node occurring as the second or third operand of a conditional expression
* @param destType the type to promote the value to
* @return a Node with the value promoted to the destType, which may be the input node
*/
protected Node conditionalExprPromotion(Node node, TypeMirror destType) {
// For rules on converting operands of conditional expressions,
// JLS 15.25
TypeMirror nodeType = node.getType();
// If the operand is already the same type as the whole
// expression, then do nothing.
if (types.isSameType(nodeType, destType)) {
return node;
}
// If the operand is a primitive and the whole expression is
// boxed, then apply boxing.
if (TypesUtils.isPrimitive(nodeType) && TypesUtils.isBoxedPrimitive(destType)) {
return box(node);
}
// If the operand is byte or Byte and the whole expression is
// short, then convert to short.
boolean isBoxedPrimitive = TypesUtils.isBoxedPrimitive(nodeType);
TypeMirror unboxedNodeType = isBoxedPrimitive ? types.unboxedType(nodeType) : nodeType;
TypeMirror unboxedDestType =
TypesUtils.isBoxedPrimitive(destType) ? types.unboxedType(destType) : destType;
if (TypesUtils.isNumeric(unboxedNodeType) && TypesUtils.isNumeric(unboxedDestType)) {
if (unboxedNodeType.getKind() == TypeKind.BYTE
&& destType.getKind() == TypeKind.SHORT) {
if (isBoxedPrimitive) {
node = unbox(node);
}
return widen(node, destType);
}
// If the operand is Byte, Short or Character and the whole expression
// is the unboxed version of it, then apply unboxing.
TypeKind destKind = destType.getKind();
if (destKind == TypeKind.BYTE
|| destKind == TypeKind.CHAR
|| destKind == TypeKind.SHORT) {
if (isBoxedPrimitive) {
return unbox(node);
} else if (nodeType.getKind() == TypeKind.INT) {
return narrow(node, destType);
}
}
return binaryNumericPromotion(node, destType);
}
// For the final case in JLS 15.25, apply boxing but not lub.
if (TypesUtils.isPrimitive(nodeType)
&& (destType.getKind() == TypeKind.DECLARED
|| destType.getKind() == TypeKind.UNION
|| destType.getKind() == TypeKind.INTERSECTION)) {
return box(node);
}
return node;
}
/**
* Returns the label {@link Name} of the leaf in the argument path, or null if the leaf is not a
* labeled statement.
*/
protected @Nullable Name getLabel(TreePath path) {
if (path.getParentPath() != null) {
Tree parent = path.getParentPath().getLeaf();
if (parent.getKind() == Tree.Kind.LABELED_STATEMENT) {
return ((LabeledStatementTree) parent).getLabel();
}
}
return null;
}
/* --------------------------------------------------------- */
/* Visitor Methods */
/* --------------------------------------------------------- */
@Override
public Node visitAnnotatedType(AnnotatedTypeTree tree, Void p) {
return scan(tree.getUnderlyingType(), p);
}
@Override
public Node visitAnnotation(AnnotationTree tree, Void p) {
throw new BugInCF("AnnotationTree is unexpected in AST to CFG translation");
}
@Override
public MethodInvocationNode visitMethodInvocation(MethodInvocationTree tree, Void p) {
// see JLS 15.12.4
// First, compute the receiver, if any (15.12.4.1).
// Second, evaluate the actual arguments, left to right and possibly some arguments are
// stored into an array for varargs calls (15.12.4.2).
// Third, test the receiver, if any, for nullness (15.12.4.4).
// Fourth, convert the arguments to the type of the formal parameters (15.12.4.5).
// Fifth, if the method is synchronized, lock the receiving object or class (15.12.4.5).
ExecutableElement method = TreeUtils.elementFromUse(tree);
if (method == null) {
// The method wasn't found, e.g. because of a compilation error.
return null;
}
ExpressionTree methodSelect = tree.getMethodSelect();
assert TreeUtils.isMethodAccess(methodSelect)
: "Expected a method access, but got: " + methodSelect;
List extends ExpressionTree> actualExprs = tree.getArguments();
// Look up method to invoke and possibly throw NullPointerException
Node receiver = getReceiver(methodSelect);
MethodAccessNode target = new MethodAccessNode(methodSelect, method, receiver);
if (ElementUtils.isStatic(method) || receiver instanceof ThisNode) {
// No NullPointerException can be thrown, use normal node
extendWithNode(target);
} else {
extendWithNodeWithException(target, nullPointerExceptionType);
}
List arguments;
if (TreeUtils.isEnumSuperCall(tree)) {
// Don't convert arguments for enum super calls. The AST contains no actual arguments,
// while the method element expects two arguments, leading to an exception in
// convertCallArguments.
// Since no actual arguments are present in the AST that is being checked, it shouldn't
// cause any harm to omit the conversions.
// See also BaseTypeVisitor.visitMethodInvocation and QualifierPolymorphism.annotate.
arguments = Collections.emptyList();
} else {
arguments =
convertCallArguments(
tree, method, TreeUtils.typeFromUse(tree), actualExprs, null);
}
// TODO: lock the receiver for synchronized methods
MethodInvocationNode node =
new MethodInvocationNode(tree, target, arguments, getCurrentPath());
ExtendedNode extendedNode = extendWithMethodInvocationNode(method, node);
/* Check for the TerminatesExecution annotation. */
boolean terminatesExecution =
annotationProvider.getDeclAnnotation(method, TerminatesExecution.class) != null;
if (terminatesExecution) {
extendedNode.setTerminatesExecution(true);
}
return node;
}
/**
* Extends the CFG with a MethodInvocationNode, accounting for potential exceptions thrown by
* the invocation.
*
* @param method the invoked method
* @param node the invocation
* @return an ExtendedNode representing the invocation and its possible thrown exceptions
*/
private ExtendedNode extendWithMethodInvocationNode(
ExecutableElement method, MethodInvocationNode node) {
List extends TypeMirror> thrownTypes = method.getThrownTypes();
Set thrownSet =
new LinkedHashSet<>(thrownTypes.size() + uncheckedExceptionTypes.size());
// Add exceptions explicitly mentioned in the throws clause.
thrownSet.addAll(thrownTypes);
// Add types to account for unchecked exceptions
thrownSet.addAll(uncheckedExceptionTypes);
return extendWithNodeWithExceptions(node, thrownSet);
}
@Override
public Node visitAssert(AssertTree tree, Void p) {
// see JLS 14.10
// If assertions are enabled, then we can just translate the assertion.
if (assumeAssertionsEnabled || assumeAssertionsEnabledFor(tree)) {
translateAssertWithAssertionsEnabled(tree);
return null;
}
// If assertions are disabled, then nothing is executed.
if (assumeAssertionsDisabled) {
return null;
}
// Otherwise, we don't know if assertions are enabled, so we use a
// variable "ea" and case-split on it. One branch does execute the
// assertion, while the other assumes assertions are disabled.
VariableTree ea = getAssertionsEnabledVariable();
// all necessary labels
Label assertionEnabled = new Label();
Label assertionDisabled = new Label();
extendWithNode(new LocalVariableNode(ea));
extendWithExtendedNode(new ConditionalJump(assertionEnabled, assertionDisabled));
// 'then' branch (i.e. check the assertion)
addLabelForNextNode(assertionEnabled);
translateAssertWithAssertionsEnabled(tree);
// 'else' branch
addLabelForNextNode(assertionDisabled);
return null;
}
/**
* Should assertions be assumed to be executed for a given {@link AssertTree}? False by default.
*/
protected boolean assumeAssertionsEnabledFor(AssertTree tree) {
return false;
}
/** The {@link VariableTree} that indicates whether assertions are enabled or not. */
protected VariableTree ea = null;
/**
* Get a synthetic {@link VariableTree} that indicates whether assertions are enabled or not.
*/
protected VariableTree getAssertionsEnabledVariable() {
if (ea == null) {
String name = uniqueName("assertionsEnabled");
Element owner = findOwner();
ExpressionTree initializer = null;
ea =
treeBuilder.buildVariableDecl(
types.getPrimitiveType(TypeKind.BOOLEAN), name, owner, initializer);
handleArtificialTree(ea);
}
return ea;
}
/**
* Find nearest owner element (Method or Class) which holds current tree.
*
* @return nearest owner element of current tree
*/
private Element findOwner() {
MethodTree enclosingMethod = TreePathUtil.enclosingMethod(getCurrentPath());
if (enclosingMethod != null) {
return TreeUtils.elementFromDeclaration(enclosingMethod);
} else {
ClassTree enclosingClass = TreePathUtil.enclosingClass(getCurrentPath());
return TreeUtils.elementFromDeclaration(enclosingClass);
}
}
/**
* Translates an assertion statement to the correct CFG nodes. The translation assumes that
* assertions are enabled.
*/
protected void translateAssertWithAssertionsEnabled(AssertTree tree) {
// all necessary labels
Label assertEnd = new Label();
Label elseEntry = new Label();
// basic block for the condition
Node condition = unbox(scan(tree.getCondition(), null));
ConditionalJump cjump = new ConditionalJump(assertEnd, elseEntry);
extendWithExtendedNode(cjump);
// else branch
Node detail = null;
addLabelForNextNode(elseEntry);
if (tree.getDetail() != null) {
detail = scan(tree.getDetail(), null);
}
AssertionErrorNode assertNode =
new AssertionErrorNode(tree, condition, detail, assertionErrorType);
extendWithNode(assertNode);
NodeWithExceptionsHolder exNode =
extendWithNodeWithException(
new ThrowNode(null, assertNode, env.getTypeUtils()), assertionErrorType);
exNode.setTerminatesExecution(true);
// then branch (nothing happens)
addLabelForNextNode(assertEnd);
}
/**
* Translates a method marked as {@link AssertMethod} into CFG nodes corresponding to an {@code
* assert} statement.
*
* @param tree the method invocation tree for a method marked as {@link AssertMethod}
* @param assertMethodTuple the assert method tuple for the method
* @param condition the boolean expression node for the argument that the method tests
*/
protected void treatMethodAsAssert(
MethodInvocationTree tree, AssertMethodTuple assertMethodTuple, Node condition) {
// all necessary labels
Label thenLabel = new Label();
Label elseLabel = new Label();
ConditionalJump cjump = new ConditionalJump(thenLabel, elseLabel);
extendWithExtendedNode(cjump);
addLabelForNextNode(assertMethodTuple.isAssertFalse ? thenLabel : elseLabel);
AssertionErrorNode assertNode =
new AssertionErrorNode(tree, condition, null, assertMethodTuple.exceptionType);
extendWithNode(assertNode);
NodeWithExceptionsHolder exNode =
extendWithNodeWithException(
new ThrowNode(null, assertNode, env.getTypeUtils()),
assertMethodTuple.exceptionType);
exNode.setTerminatesExecution(true);
addLabelForNextNode(assertMethodTuple.isAssertFalse ? elseLabel : thenLabel);
}
@Override
public Node visitAssignment(AssignmentTree tree, Void p) {
// see JLS 15.26.1
AssignmentNode assignmentNode;
ExpressionTree variable = tree.getVariable();
TypeMirror varType = TreeUtils.typeOf(variable);
// case 1: lhs is field access
if (TreeUtils.isFieldAccess(variable)) {
// visit receiver
Node receiver = getReceiver(variable);
// visit expression
Node expression = scan(tree.getExpression(), p);
expression = assignConvert(expression, varType);
// visit field access (throws null-pointer exception)
FieldAccessNode target = new FieldAccessNode(variable, receiver);
target.setLValue();
Element element = TreeUtils.elementFromUse(variable);
if (ElementUtils.isStatic(element) || receiver instanceof ThisNode) {
// No NullPointerException can be thrown, use normal node
extendWithNode(target);
} else {
extendWithNodeWithException(target, nullPointerExceptionType);
}
// add assignment node
assignmentNode = new AssignmentNode(tree, target, expression);
extendWithNode(assignmentNode);
}
// case 2: lhs is not a field access
else {
Node target = scan(variable, p);
target.setLValue();
assignmentNode = translateAssignment(tree, target, tree.getExpression());
}
return assignmentNode;
}
/** Translate an assignment. */
protected AssignmentNode translateAssignment(Tree tree, Node target, ExpressionTree rhs) {
Node expression = scan(rhs, null);
return translateAssignment(tree, target, expression);
}
/** Translate an assignment where the RHS has already been scanned. */
protected AssignmentNode translateAssignment(Tree tree, Node target, Node expression) {
assert tree instanceof AssignmentTree || tree instanceof VariableTree;
target.setLValue();
expression = assignConvert(expression, target.getType());
AssignmentNode assignmentNode = new AssignmentNode(tree, target, expression);
extendWithNode(assignmentNode);
return assignmentNode;
}
/**
* Note 1: Requires {@code tree} to be a field or method access tree.
*
* Note 2: Visits the receiver and adds all necessary blocks to the CFG.
*
* @param tree the field or method access tree containing the receiver: one of
* MethodInvocationTree, AssignmentTree, or IdentifierTree
* @return the receiver of the field or method access
*/
private Node getReceiver(ExpressionTree tree) {
assert TreeUtils.isFieldAccess(tree) || TreeUtils.isMethodAccess(tree);
if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
// `tree` has an explicit receiver.
MemberSelectTree mtree = (MemberSelectTree) tree;
return scan(mtree.getExpression(), null);
}
// Access through an implicit receiver
Element ele = TreeUtils.elementFromUse(tree);
TypeElement declClassElem = ElementUtils.enclosingTypeElement(ele);
TypeMirror declClassType = ElementUtils.getType(declClassElem);
if (ElementUtils.isStatic(ele)) {
ClassNameNode node = new ClassNameNode(declClassType, declClassElem);
extendWithClassNameNode(node);
return node;
}
// Access through an implicit `this`
TreePath enclClassPath = TreePathUtil.pathTillClass(getCurrentPath());
ClassTree enclClassTree = (ClassTree) enclClassPath.getLeaf();
TypeElement enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree);
TypeMirror enclClassType = enclClassElem.asType();
while (!TypesUtils.isErasedSubtype(enclClassType, declClassType, types)) {
enclClassPath = TreePathUtil.pathTillClass(enclClassPath.getParentPath());
if (enclClassPath == null) {
enclClassType = declClassType;
break;
}
enclClassTree = (ClassTree) enclClassPath.getLeaf();
enclClassElem = TreeUtils.elementFromDeclaration(enclClassTree);
enclClassType = enclClassElem.asType();
}
Node node = new ImplicitThisNode(enclClassType);
extendWithNode(node);
return node;
}
/**
* Map an operation with assignment to the corresponding operation without assignment.
*
* @param kind a Tree.Kind representing an operation with assignment
* @return the Tree.Kind for the same operation without assignment
*/
protected Tree.Kind withoutAssignment(Tree.Kind kind) {
switch (kind) {
case DIVIDE_ASSIGNMENT:
return Tree.Kind.DIVIDE;
case MULTIPLY_ASSIGNMENT:
return Tree.Kind.MULTIPLY;
case REMAINDER_ASSIGNMENT:
return Tree.Kind.REMAINDER;
case MINUS_ASSIGNMENT:
return Tree.Kind.MINUS;
case PLUS_ASSIGNMENT:
return Tree.Kind.PLUS;
case LEFT_SHIFT_ASSIGNMENT:
return Tree.Kind.LEFT_SHIFT;
case RIGHT_SHIFT_ASSIGNMENT:
return Tree.Kind.RIGHT_SHIFT;
case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
return Tree.Kind.UNSIGNED_RIGHT_SHIFT;
case AND_ASSIGNMENT:
return Tree.Kind.AND;
case OR_ASSIGNMENT:
return Tree.Kind.OR;
case XOR_ASSIGNMENT:
return Tree.Kind.XOR;
default:
return Tree.Kind.ERRONEOUS;
}
}
@Override
public Node visitCompoundAssignment(CompoundAssignmentTree tree, Void p) {
// According the JLS 15.26.2, E1 op= E2 is equivalent to
// E1 = (T) ((E1) op (E2)), where T is the type of E1,
// except that E1 is evaluated only once.
//
Tree.Kind kind = tree.getKind();
switch (kind) {
case DIVIDE_ASSIGNMENT:
case MULTIPLY_ASSIGNMENT:
case REMAINDER_ASSIGNMENT:
{
// see JLS 15.17 and 15.26.2
Node targetLHS = scan(tree.getVariable(), p);
Node value = scan(tree.getExpression(), p);
TypeMirror exprType = TreeUtils.typeOf(tree);
TypeMirror leftType = TreeUtils.typeOf(tree.getVariable());
TypeMirror rightType = TreeUtils.typeOf(tree.getExpression());
TypeMirror promotedType = binaryPromotedType(leftType, rightType);
Node targetRHS = binaryNumericPromotion(targetLHS, promotedType);
value = binaryNumericPromotion(value, promotedType);
BinaryTree operTree =
treeBuilder.buildBinary(
promotedType,
withoutAssignment(kind),
tree.getVariable(),
tree.getExpression());
handleArtificialTree(operTree);
Node operNode;
if (kind == Tree.Kind.MULTIPLY_ASSIGNMENT) {
operNode = new NumericalMultiplicationNode(operTree, targetRHS, value);
} else if (kind == Tree.Kind.DIVIDE_ASSIGNMENT) {
if (TypesUtils.isIntegralPrimitive(exprType)) {
operNode = new IntegerDivisionNode(operTree, targetRHS, value);
extendWithNodeWithException(operNode, arithmeticExceptionType);
} else {
operNode = new FloatingDivisionNode(operTree, targetRHS, value);
}
} else {
assert kind == Tree.Kind.REMAINDER_ASSIGNMENT;
if (TypesUtils.isIntegralPrimitive(exprType)) {
operNode = new IntegerRemainderNode(operTree, targetRHS, value);
extendWithNodeWithException(operNode, arithmeticExceptionType);
} else {
operNode = new FloatingRemainderNode(operTree, targetRHS, value);
}
}
extendWithNode(operNode);
TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType);
TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree);
handleArtificialTree(castTree);
TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types);
castNode.setInSource(false);
extendWithNode(castNode);
AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode);
extendWithNode(assignNode);
return assignNode;
}
case MINUS_ASSIGNMENT:
case PLUS_ASSIGNMENT:
{
// see JLS 15.18 and 15.26.2
Node targetLHS = scan(tree.getVariable(), p);
Node value = scan(tree.getExpression(), p);
TypeMirror leftType = TreeUtils.typeOf(tree.getVariable());
TypeMirror rightType = TreeUtils.typeOf(tree.getExpression());
if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) {
assert (kind == Tree.Kind.PLUS_ASSIGNMENT);
Node targetRHS = stringConversion(targetLHS);
value = stringConversion(value);
BinaryTree operTree =
treeBuilder.buildBinary(
leftType,
withoutAssignment(kind),
tree.getVariable(),
tree.getExpression());
handleArtificialTree(operTree);
Node operNode = new StringConcatenateNode(operTree, targetRHS, value);
extendWithNode(operNode);
AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, operNode);
extendWithNode(assignNode);
return assignNode;
} else {
TypeMirror promotedType = binaryPromotedType(leftType, rightType);
Node targetRHS = binaryNumericPromotion(targetLHS, promotedType);
value = binaryNumericPromotion(value, promotedType);
BinaryTree operTree =
treeBuilder.buildBinary(
promotedType,
withoutAssignment(kind),
tree.getVariable(),
tree.getExpression());
handleArtificialTree(operTree);
Node operNode;
if (kind == Tree.Kind.PLUS_ASSIGNMENT) {
operNode = new NumericalAdditionNode(operTree, targetRHS, value);
} else {
assert kind == Tree.Kind.MINUS_ASSIGNMENT;
operNode = new NumericalSubtractionNode(operTree, targetRHS, value);
}
extendWithNode(operNode);
TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType);
TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree);
handleArtificialTree(castTree);
TypeCastNode castNode =
new TypeCastNode(castTree, operNode, castType, types);
castNode.setInSource(false);
extendWithNode(castNode);
// Map the compound assignment tree to an assignment node, which
// will have the correct type.
AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode);
extendWithNode(assignNode);
return assignNode;
}
}
case LEFT_SHIFT_ASSIGNMENT:
case RIGHT_SHIFT_ASSIGNMENT:
case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
{
// see JLS 15.19 and 15.26.2
Node targetLHS = scan(tree.getVariable(), p);
Node value = scan(tree.getExpression(), p);
TypeMirror leftType = TreeUtils.typeOf(tree.getVariable());
Node targetRHS = unaryNumericPromotion(targetLHS);
value = unaryNumericPromotion(value);
BinaryTree operTree =
treeBuilder.buildBinary(
leftType,
withoutAssignment(kind),
tree.getVariable(),
tree.getExpression());
handleArtificialTree(operTree);
Node operNode;
if (kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT) {
operNode = new LeftShiftNode(operTree, targetRHS, value);
} else if (kind == Tree.Kind.RIGHT_SHIFT_ASSIGNMENT) {
operNode = new SignedRightShiftNode(operTree, targetRHS, value);
} else {
assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT;
operNode = new UnsignedRightShiftNode(operTree, targetRHS, value);
}
extendWithNode(operNode);
TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType);
TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree);
handleArtificialTree(castTree);
TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types);
castNode.setInSource(false);
extendWithNode(castNode);
AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode);
extendWithNode(assignNode);
return assignNode;
}
case AND_ASSIGNMENT:
case OR_ASSIGNMENT:
case XOR_ASSIGNMENT:
// see JLS 15.22
Node targetLHS = scan(tree.getVariable(), p);
Node value = scan(tree.getExpression(), p);
TypeMirror leftType = TreeUtils.typeOf(tree.getVariable());
TypeMirror rightType = TreeUtils.typeOf(tree.getExpression());
Node targetRHS = null;
if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) {
TypeMirror promotedType = binaryPromotedType(leftType, rightType);
targetRHS = binaryNumericPromotion(targetLHS, promotedType);
value = binaryNumericPromotion(value, promotedType);
} else if (TypesUtils.isBooleanType(leftType)
&& TypesUtils.isBooleanType(rightType)) {
targetRHS = unbox(targetLHS);
value = unbox(value);
} else {
throw new BugInCF(
"Both arguments to logical operation must be numeric or boolean");
}
BinaryTree operTree =
treeBuilder.buildBinary(
leftType,
withoutAssignment(kind),
tree.getVariable(),
tree.getExpression());
handleArtificialTree(operTree);
Node operNode;
if (kind == Tree.Kind.AND_ASSIGNMENT) {
operNode = new BitwiseAndNode(operTree, targetRHS, value);
} else if (kind == Tree.Kind.OR_ASSIGNMENT) {
operNode = new BitwiseOrNode(operTree, targetRHS, value);
} else {
assert kind == Tree.Kind.XOR_ASSIGNMENT;
operNode = new BitwiseXorNode(operTree, targetRHS, value);
}
extendWithNode(operNode);
TypeMirror castType = TypeAnnotationUtils.unannotatedType(leftType);
TypeCastTree castTree = treeBuilder.buildTypeCast(castType, operTree);
handleArtificialTree(castTree);
TypeCastNode castNode = new TypeCastNode(castTree, operNode, castType, types);
castNode.setInSource(false);
extendWithNode(castNode);
AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode);
extendWithNode(assignNode);
return assignNode;
default:
throw new BugInCF("unexpected compound assignment type");
}
}
@Override
public Node visitBinary(BinaryTree tree, Void p) {
// Note that for binary operations it is important to perform any required promotion on the
// left operand before generating any Nodes for the right operand, because labels must be
// inserted AFTER ALL preceding Nodes and BEFORE ALL following Nodes.
Node r = null;
Tree leftTree = tree.getLeftOperand();
Tree rightTree = tree.getRightOperand();
Tree.Kind kind = tree.getKind();
switch (kind) {
case DIVIDE:
case MULTIPLY:
case REMAINDER:
{
// see JLS 15.17
TypeMirror exprType = TreeUtils.typeOf(tree);
TypeMirror leftType = TreeUtils.typeOf(leftTree);
TypeMirror rightType = TreeUtils.typeOf(rightTree);
TypeMirror promotedType = binaryPromotedType(leftType, rightType);
Node left = binaryNumericPromotion(scan(leftTree, p), promotedType);
Node right = binaryNumericPromotion(scan(rightTree, p), promotedType);
if (kind == Tree.Kind.MULTIPLY) {
r = new NumericalMultiplicationNode(tree, left, right);
} else if (kind == Tree.Kind.DIVIDE) {
if (TypesUtils.isIntegralPrimitive(exprType)) {
r = new IntegerDivisionNode(tree, left, right);
extendWithNodeWithException(r, arithmeticExceptionType);
} else {
r = new FloatingDivisionNode(tree, left, right);
}
} else {
assert kind == Tree.Kind.REMAINDER;
if (TypesUtils.isIntegralPrimitive(exprType)) {
r = new IntegerRemainderNode(tree, left, right);
extendWithNodeWithException(r, arithmeticExceptionType);
} else {
r = new FloatingRemainderNode(tree, left, right);
}
}
break;
}
case MINUS:
case PLUS:
{
// see JLS 15.18
// TypeMirror exprType = InternalUtils.typeOf(tree);
TypeMirror leftType = TreeUtils.typeOf(leftTree);
TypeMirror rightType = TreeUtils.typeOf(rightTree);
if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) {
assert (kind == Tree.Kind.PLUS);
Node left = stringConversion(scan(leftTree, p));
Node right = stringConversion(scan(rightTree, p));
r = new StringConcatenateNode(tree, left, right);
} else {
TypeMirror promotedType = binaryPromotedType(leftType, rightType);
Node left = binaryNumericPromotion(scan(leftTree, p), promotedType);
Node right = binaryNumericPromotion(scan(rightTree, p), promotedType);
// TODO: Decide whether to deal with floating-point value
// set conversion.
if (kind == Tree.Kind.PLUS) {
r = new NumericalAdditionNode(tree, left, right);
} else {
assert kind == Tree.Kind.MINUS;
r = new NumericalSubtractionNode(tree, left, right);
}
}
break;
}
case LEFT_SHIFT:
case RIGHT_SHIFT:
case UNSIGNED_RIGHT_SHIFT:
{
// see JLS 15.19
Node left = unaryNumericPromotion(scan(leftTree, p));
Node right = unaryNumericPromotion(scan(rightTree, p));
if (kind == Tree.Kind.LEFT_SHIFT) {
r = new LeftShiftNode(tree, left, right);
} else if (kind == Tree.Kind.RIGHT_SHIFT) {
r = new SignedRightShiftNode(tree, left, right);
} else {
assert kind == Tree.Kind.UNSIGNED_RIGHT_SHIFT;
r = new UnsignedRightShiftNode(tree, left, right);
}
break;
}
case GREATER_THAN:
case GREATER_THAN_EQUAL:
case LESS_THAN:
case LESS_THAN_EQUAL:
{
// see JLS 15.20.1
TypeMirror leftType = TreeUtils.typeOf(leftTree);
if (TypesUtils.isBoxedPrimitive(leftType)) {
leftType = types.unboxedType(leftType);
}
TypeMirror rightType = TreeUtils.typeOf(rightTree);
if (TypesUtils.isBoxedPrimitive(rightType)) {
rightType = types.unboxedType(rightType);
}
TypeMirror promotedType = binaryPromotedType(leftType, rightType);
Node left = binaryNumericPromotion(scan(leftTree, p), promotedType);
Node right = binaryNumericPromotion(scan(rightTree, p), promotedType);
Node node;
if (kind == Tree.Kind.GREATER_THAN) {
node = new GreaterThanNode(tree, left, right);
} else if (kind == Tree.Kind.GREATER_THAN_EQUAL) {
node = new GreaterThanOrEqualNode(tree, left, right);
} else if (kind == Tree.Kind.LESS_THAN) {
node = new LessThanNode(tree, left, right);
} else {
assert kind == Tree.Kind.LESS_THAN_EQUAL;
node = new LessThanOrEqualNode(tree, left, right);
}
extendWithNode(node);
return node;
}
case EQUAL_TO:
case NOT_EQUAL_TO:
{
// see JLS 15.21
TreeInfo leftInfo = getTreeInfo(leftTree);
TreeInfo rightInfo = getTreeInfo(rightTree);
Node left = scan(leftTree, p);
Node right = scan(rightTree, p);
if (leftInfo.isNumeric()
&& rightInfo.isNumeric()
&& !(leftInfo.isBoxed() && rightInfo.isBoxed())) {
// JLS 15.21.1 numerical equality
TypeMirror promotedType =
binaryPromotedType(leftInfo.unboxedType(), rightInfo.unboxedType());
left = binaryNumericPromotion(left, promotedType);
right = binaryNumericPromotion(right, promotedType);
} else if (leftInfo.isBoolean()
&& rightInfo.isBoolean()
&& !(leftInfo.isBoxed() && rightInfo.isBoxed())) {
// JSL 15.21.2 boolean equality
left = unboxAsNeeded(left, leftInfo.isBoxed());
right = unboxAsNeeded(right, rightInfo.isBoxed());
}
Node node;
if (kind == Tree.Kind.EQUAL_TO) {
node = new EqualToNode(tree, left, right);
} else {
assert kind == Tree.Kind.NOT_EQUAL_TO;
node = new NotEqualNode(tree, left, right);
}
extendWithNode(node);
return node;
}
case AND:
case OR:
case XOR:
{
// see JLS 15.22
TypeMirror leftType = TreeUtils.typeOf(leftTree);
TypeMirror rightType = TreeUtils.typeOf(rightTree);
boolean isBooleanOp =
TypesUtils.isBooleanType(leftType)
&& TypesUtils.isBooleanType(rightType);
Node left;
Node right;
if (isBooleanOp) {
left = unbox(scan(leftTree, p));
right = unbox(scan(rightTree, p));
} else if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) {
TypeMirror promotedType = binaryPromotedType(leftType, rightType);
left = binaryNumericPromotion(scan(leftTree, p), promotedType);
right = binaryNumericPromotion(scan(rightTree, p), promotedType);
} else {
left = unbox(scan(leftTree, p));
right = unbox(scan(rightTree, p));
}
Node node;
if (kind == Tree.Kind.AND) {
node = new BitwiseAndNode(tree, left, right);
} else if (kind == Tree.Kind.OR) {
node = new BitwiseOrNode(tree, left, right);
} else {
assert kind == Tree.Kind.XOR;
node = new BitwiseXorNode(tree, left, right);
}
extendWithNode(node);
return node;
}
case CONDITIONAL_AND:
case CONDITIONAL_OR:
{
// see JLS 15.23 and 15.24
// all necessary labels
Label rightStartLabel = new Label();
Label shortCircuitLabel = new Label();
// left-hand side
Node left = scan(leftTree, p);
ConditionalJump cjump;
if (kind == Tree.Kind.CONDITIONAL_AND) {
cjump = new ConditionalJump(rightStartLabel, shortCircuitLabel);
cjump.setFalseFlowRule(FlowRule.ELSE_TO_ELSE);
} else {
cjump = new ConditionalJump(shortCircuitLabel, rightStartLabel);
cjump.setTrueFlowRule(FlowRule.THEN_TO_THEN);
}
extendWithExtendedNode(cjump);
// right-hand side
addLabelForNextNode(rightStartLabel);
Node right = scan(rightTree, p);
// conditional expression itself
addLabelForNextNode(shortCircuitLabel);
Node node;
if (kind == Tree.Kind.CONDITIONAL_AND) {
node = new ConditionalAndNode(tree, left, right);
} else {
node = new ConditionalOrNode(tree, left, right);
}
extendWithNode(node);
return node;
}
default:
throw new BugInCF("unexpected binary tree: " + kind);
}
assert r != null : "unexpected binary tree";
extendWithNode(r);
return r;
}
@Override
public Node visitBlock(BlockTree tree, Void p) {
for (StatementTree n : tree.getStatements()) {
scan(n, null);
}
return null;
}
@Override
public Node visitBreak(BreakTree tree, Void p) {
Name label = tree.getLabel();
if (label == null) {
assert breakTargetLC != null : "no target for break statement";
extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel()));
} else {
assert breakLabels.containsKey(label);
extendWithExtendedNode(new UnconditionalJump(breakLabels.get(label)));
}
return null;
}
// This visits a switch statement.
// Switch expressions are visited by visitSwitchExpression17.
@Override
public Node visitSwitch(SwitchTree tree, Void p) {
SwitchBuilder builder = new SwitchBuilder(tree);
builder.build();
return null;
}
/**
* Helper class for handling switch statements and switch expressions, including all their
* substatements such as case labels.
*/
private class SwitchBuilder {
/**
* The tree for the switch statement or switch expression. Its type may be {@link
* SwitchTree} (for a switch statement) or {@code SwitchExpressionTree}.
*/
private final Tree switchTree;
/** The case trees of {@code switchTree} */
private final List extends CaseTree> caseTrees;
/**
* The Tree for the selector expression.
*
*
* switch ( selector expression ) { ... }
*
*/
private final ExpressionTree selectorExprTree;
/** The labels for the case bodies. */
private final Label[] caseBodyLabels;
/**
* The Node for the assignment of the switch selector expression to a synthetic local
* variable.
*/
private AssignmentNode selectorExprAssignment;
/**
* If {@link #switchTree} is a switch expression, then this is a result variable: the
* synthetic variable that all results of {@code #switchTree} are assigned to. Otherwise,
* this is null.
*/
private @Nullable VariableTree switchExprVarTree;
/**
* Construct a SwitchBuilder.
*
* @param switchTree a {@link SwitchTree} or a {@code SwitchExpressionTree}
*/
private SwitchBuilder(Tree switchTree) {
this.switchTree = switchTree;
if (TreeUtils.isSwitchStatement(switchTree)) {
SwitchTree switchStatementTree = (SwitchTree) switchTree;
this.caseTrees = switchStatementTree.getCases();
this.selectorExprTree = switchStatementTree.getExpression();
} else {
this.caseTrees = SwitchExpressionUtils.getCases(switchTree);
this.selectorExprTree = SwitchExpressionUtils.getExpression(switchTree);
}
// "+ 1" for the default case. If the switch has an explicit default case, then
// the last element of the array is never used.
this.caseBodyLabels = new Label[caseTrees.size() + 1];
}
/**
* Build up the CFG for the switchTree.
*
* @return if the switch is a switch expression, then a {@link SwitchExpressionNode};
* otherwise, null
*/
public @Nullable SwitchExpressionNode build() {
LabelCell oldBreakTargetLC = breakTargetLC;
breakTargetLC = new LabelCell(new Label());
int numCases = caseTrees.size();
for (int i = 0; i < numCases; ++i) {
caseBodyLabels[i] = new Label();
}
caseBodyLabels[numCases] = breakTargetLC.peekLabel();
buildSelector();
buildSwitchExpressionVar();
if (TreeUtils.isSwitchStatement(switchTree)) {
// It's a switch statement, not a switch expression.
extendWithNode(
new MarkerNode(
switchTree,
"start of switch statement #" + TreeUtils.treeUids.get(switchTree),
env.getTypeUtils()));
}
// JSL 14.11.2
// https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11.2
// states "For compatibility reasons, switch statements that are not enhanced switch
// statements are not required to be exhaustive".
// Switch expressions and enhanced switch statements are exhaustive.
boolean switchExprOrEnhanced =
!TreeUtils.isSwitchStatement(switchTree)
|| TreeUtils.isEnhancedSwitchStatement((SwitchTree) switchTree);
// Build CFG for the cases.
int defaultIndex = -1;
for (int i = 0; i < numCases; ++i) {
CaseTree caseTree = caseTrees.get(i);
if (CaseUtils.isDefaultCaseTree(caseTree)) {
// Per the Java Language Specification, the checks of all cases must happen
// before the default case, no matter where `default:` is written. Therefore,
// build the default case last.
defaultIndex = i;
} else if (i == numCases - 1 && defaultIndex == -1) {
// This is the last case, and there is no default case.
// Switch expressions and enhanced switch statements are exhaustive.
buildCase(caseTree, i, switchExprOrEnhanced);
} else {
buildCase(caseTree, i, false);
}
}
if (defaultIndex != -1) {
// The checks of all cases must happen before the default case, therefore we build
// the default case last.
// Fallthrough is still handled correctly with the caseBodyLabels.
buildCase(caseTrees.get(defaultIndex), defaultIndex, false);
}
addLabelForNextNode(breakTargetLC.peekLabel());
breakTargetLC = oldBreakTargetLC;
if (TreeUtils.isSwitchStatement(switchTree)) {
// It's a switch statement, not a switch expression.
extendWithNode(
new MarkerNode(
switchTree,
"end of switch statement #" + TreeUtils.treeUids.get(switchTree),
env.getTypeUtils()));
}
if (!TreeUtils.isSwitchStatement(switchTree)) {
// It's a switch expression, not a switch statement.
IdentifierTree switchExprVarUseTree =
treeBuilder.buildVariableUse(switchExprVarTree);
handleArtificialTree(switchExprVarUseTree);
LocalVariableNode switchExprVarUseNode =
new LocalVariableNode(switchExprVarUseTree);
switchExprVarUseNode.setInSource(false);
extendWithNode(switchExprVarUseNode);
SwitchExpressionNode switchExpressionNode =
new SwitchExpressionNode(
TreeUtils.typeOf(switchTree), switchTree, switchExprVarUseNode);
extendWithNode(switchExpressionNode);
return switchExpressionNode;
} else {
return null;
}
}
/**
* Builds the CFG for the selector expression. It also creates a synthetic variable and
* assigns the selector expression to the variable. This assignment node is stored in {@link
* #selectorExprAssignment}. It can later be used to refine the selector expression in case
* bodies.
*/
private void buildSelector() {
// Create a synthetic variable to which the switch selector expression will be assigned
TypeMirror selectorExprType = TreeUtils.typeOf(selectorExprTree);
VariableTree selectorVarTree =
treeBuilder.buildVariableDecl(
selectorExprType, uniqueName("switch"), findOwner(), null);
handleArtificialTree(selectorVarTree);
VariableDeclarationNode selectorVarNode = new VariableDeclarationNode(selectorVarTree);
selectorVarNode.setInSource(false);
extendWithNode(selectorVarNode);
IdentifierTree selectorVarUseTree = treeBuilder.buildVariableUse(selectorVarTree);
handleArtificialTree(selectorVarUseTree);
LocalVariableNode selectorVarUseNode = new LocalVariableNode(selectorVarUseTree);
selectorVarUseNode.setInSource(false);
extendWithNode(selectorVarUseNode);
Node selectorExprNode = unbox(scan(selectorExprTree, null));
AssignmentTree assign =
treeBuilder.buildAssignment(selectorVarUseTree, selectorExprTree);
handleArtificialTree(assign);
selectorExprAssignment =
new AssignmentNode(assign, selectorVarUseNode, selectorExprNode);
selectorExprAssignment.setInSource(false);
extendWithNode(selectorExprAssignment);
}
/**
* If {@link #switchTree} is a switch expression tree, this method creates a synthetic
* variable whose value is the value of the switch expression.
*/
private void buildSwitchExpressionVar() {
if (TreeUtils.isSwitchStatement(switchTree)) {
// A switch statement does not have a value, so do nothing.
return;
}
TypeMirror switchExprType = TreeUtils.typeOf(switchTree);
switchExprVarTree =
treeBuilder.buildVariableDecl(
switchExprType, uniqueName("switchExpr"), findOwner(), null);
handleArtificialTree(switchExprVarTree);
VariableDeclarationNode switchExprVarNode =
new VariableDeclarationNode(switchExprVarTree);
switchExprVarNode.setInSource(false);
extendWithNode(switchExprVarNode);
}
/**
* Build the CFG for the given case tree.
*
* @param caseTree a case tree whose CFG to build
* @param index the index of the case tree in {@link #caseBodyLabels}
* @param isLastCaseOfExhaustive true if this is the last case of an exhaustive switch
* statement, with no fallthrough to it. In other words, no test of the labels is
* necessary.
*/
private void buildCase(CaseTree caseTree, int index, boolean isLastCaseOfExhaustive) {
boolean isDefaultCase = CaseUtils.isDefaultCaseTree(caseTree);
// If true, no test of labels is necessary.
// Unfortunately, if isLastCaseOfExhaustive==TRUE, no flow-sensitive refinement occurs
// within the body of the CaseNode. In the future, that can be performed, but it
// requires addition of InfeasibleExitBlock, a new SpecialBlock in the CFG.
boolean isTerminalCase = isDefaultCase || isLastCaseOfExhaustive;
Label thisBodyLabel = caseBodyLabels[index];
Label nextBodyLabel = caseBodyLabels[index + 1];
// `nextCaseLabel` is not used if isTerminalCase==FALSE.
Label nextCaseLabel = new Label();
// Handle the case expressions
if (!isTerminalCase) {
// A case expression exists, and it needs to be tested.
ArrayList exprs = new ArrayList<>();
for (Tree exprTree : CaseUtils.getLabels(caseTree)) {
exprs.add(scan(exprTree, null));
}
ExpressionTree guardTree = CaseUtils.getGuard(caseTree);
Node guard = (guardTree == null) ? null : scan(guardTree, null);
CaseNode test =
new CaseNode(
caseTree, selectorExprAssignment, exprs, guard, env.getTypeUtils());
extendWithNode(test);
extendWithExtendedNode(new ConditionalJump(thisBodyLabel, nextCaseLabel));
}
// Handle the case body
addLabelForNextNode(thisBodyLabel);
if (caseTree.getStatements() != null) {
// This is a switch labeled statement group.
// A "switch labeled statement group" is a "case L:" label along with its code.
// The code either ends with a "yield" statement, or it falls through.
for (StatementTree stmt : caseTree.getStatements()) {
scan(stmt, null);
}
// Handle possible fallthrough by adding jump to next body.
if (!isTerminalCase) {
extendWithExtendedNode(new UnconditionalJump(nextBodyLabel));
}
} else {
// This is either the default case or a switch labeled rule (which appears in a
// switch expression).
// A "switch labeled rule" is a "case L ->" label along with its code.
Tree bodyTree = CaseUtils.getBody(caseTree);
if (!TreeUtils.isSwitchStatement(switchTree)
&& bodyTree instanceof ExpressionTree) {
buildSwitchExpressionResult((ExpressionTree) bodyTree);
} else {
scan(bodyTree, null);
// Switch rules never fall through so add jump to the break target.
assert breakTargetLC != null : "no target for case statement";
extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel()));
}
}
if (!isTerminalCase) {
addLabelForNextNode(nextCaseLabel);
}
}
/**
* Does the following for the result expression of a switch expression, {@code
* resultExpression}:
*
*
* Builds the CFG for the switch expression result.
* Creates an assignment node for the assignment of {@code resultExpression} to {@code
* switchExprVarTree}.
* Adds an unconditional jump to {@link #breakTargetLC} (the end of the switch
* expression).
*
*
* @param resultExpression the result of a switch expression; either from a yield or an
* expression in a case rule
*/
/*package-private*/ void buildSwitchExpressionResult(ExpressionTree resultExpression) {
IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree);
handleArtificialTree(switchExprVarUseTree);
LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree);
switchExprVarUseNode.setInSource(false);
extendWithNode(switchExprVarUseNode);
Node resultExprNode = scan(resultExpression, null);
AssignmentTree assign =
treeBuilder.buildAssignment(switchExprVarUseTree, resultExpression);
handleArtificialTree(assign);
AssignmentNode assignmentNode =
new AssignmentNode(assign, switchExprVarUseNode, resultExprNode);
assignmentNode.setInSource(false);
extendWithNode(assignmentNode);
// Switch rules never fall through so add jump to the break target.
assert breakTargetLC != null : "no target for case statement";
extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel()));
}
}
@Override
public Node visitCase(CaseTree tree, Void p) {
// This assertion assumes that `case` appears only within a switch statement,
throw new AssertionError("case visitor is implemented in SwitchBuilder");
}
@Override
public Node visitCatch(CatchTree tree, Void p) {
scan(tree.getParameter(), p);
scan(tree.getBlock(), p);
return null;
}
// This is not invoked for top-level classes. Maybe it is, for classes defined within method
// bodies.
@Override
public Node visitClass(ClassTree tree, Void p) {
declaredClasses.add(tree);
Node classbody = new ClassDeclarationNode(tree);
extendWithNode(classbody);
return classbody;
}
@Override
public Node visitConditionalExpression(ConditionalExpressionTree tree, Void p) {
// see JLS 15.25
TypeMirror exprType = TreeUtils.typeOf(tree);
Label trueStart = new Label();
Label falseStart = new Label();
Label merge = new Label();
// create a synthetic variable for the value of the conditional expression
VariableTree condExprVarTree =
treeBuilder.buildVariableDecl(exprType, uniqueName("condExpr"), findOwner(), null);
handleArtificialTree(condExprVarTree);
VariableDeclarationNode condExprVarNode = new VariableDeclarationNode(condExprVarTree);
condExprVarNode.setInSource(false);
extendWithNode(condExprVarNode);
Node condition = unbox(scan(tree.getCondition(), p));
ConditionalJump cjump = new ConditionalJump(trueStart, falseStart);
extendWithExtendedNode(cjump);
addLabelForNextNode(trueStart);
ExpressionTree trueExprTree = tree.getTrueExpression();
Node trueExprNode = scan(trueExprTree, p);
trueExprNode = conditionalExprPromotion(trueExprNode, exprType);
extendWithAssignmentForConditionalExpr(condExprVarTree, trueExprTree, trueExprNode);
extendWithExtendedNode(new UnconditionalJump(merge));
addLabelForNextNode(falseStart);
ExpressionTree falseExprTree = tree.getFalseExpression();
Node falseExprNode = scan(falseExprTree, p);
falseExprNode = conditionalExprPromotion(falseExprNode, exprType);
extendWithAssignmentForConditionalExpr(condExprVarTree, falseExprTree, falseExprNode);
extendWithExtendedNode(new UnconditionalJump(merge));
addLabelForNextNode(merge);
IPair treeAndLocalVarNode =
buildVarUseNode(condExprVarTree);
Node node =
new TernaryExpressionNode(
tree, condition, trueExprNode, falseExprNode, treeAndLocalVarNode.second);
extendWithNode(node);
return node;
}
/**
* Extend the CFG with an assignment for either the true or false case of a conditional
* expression, assigning the value of the expression for the case to the synthetic variable for
* the conditional expression
*
* @param condExprVarTree tree for synthetic variable for conditional expression
* @param caseExprTree expression tree for the case
* @param caseExprNode node for the case
*/
private void extendWithAssignmentForConditionalExpr(
VariableTree condExprVarTree, ExpressionTree caseExprTree, Node caseExprNode) {
IPair treeAndLocalVarNode =
buildVarUseNode(condExprVarTree);
AssignmentTree assign =
treeBuilder.buildAssignment(treeAndLocalVarNode.first, caseExprTree);
handleArtificialTree(assign);
// Build a "synthetic" assignment node, allowing special handling in transfer functions
AssignmentNode assignmentNode =
new AssignmentNode(assign, treeAndLocalVarNode.second, caseExprNode, true);
assignmentNode.setInSource(false);
extendWithNode(assignmentNode);
}
/**
* Build a pair of {@link IdentifierTree} and {@link LocalVariableNode} to represent a use of
* some variable. Does not add the node to the CFG.
*
* @param varTree tree for the variable
* @return a pair whose first element is the synthetic {@link IdentifierTree} for the use, and
* whose second element is the {@link LocalVariableNode} representing the use
*/
private IPair buildVarUseNode(VariableTree varTree) {
IdentifierTree condExprVarUseTree = treeBuilder.buildVariableUse(varTree);
handleArtificialTree(condExprVarUseTree);
LocalVariableNode condExprVarUseNode = new LocalVariableNode(condExprVarUseTree);
condExprVarUseNode.setInSource(false);
// Do not actually add the node to the CFG.
return IPair.of(condExprVarUseTree, condExprVarUseNode);
}
@Override
public Node visitContinue(ContinueTree tree, Void p) {
Name label = tree.getLabel();
if (label == null) {
assert continueTargetLC != null : "no target for continue statement";
extendWithExtendedNode(new UnconditionalJump(continueTargetLC.accessLabel()));
} else {
assert continueLabels.containsKey(label);
extendWithExtendedNode(new UnconditionalJump(continueLabels.get(label)));
}
return null;
}
@Override
public Node visitDoWhileLoop(DoWhileLoopTree tree, Void p) {
Name parentLabel = getLabel(getCurrentPath());
Label loopEntry = new Label();
Label loopExit = new Label();
// If the loop is a labeled statement, then its continue target is identical for continues
// with no label and continues with the loop's label.
Label conditionStart;
if (parentLabel != null) {
conditionStart = continueLabels.get(parentLabel);
} else {
conditionStart = new Label();
}
LabelCell oldBreakTargetLC = breakTargetLC;
breakTargetLC = new LabelCell(loopExit);
LabelCell oldContinueTargetLC = continueTargetLC;
continueTargetLC = new LabelCell(conditionStart);
// Loop body
addLabelForNextNode(loopEntry);
assert tree.getStatement() != null;
scan(tree.getStatement(), p);
// Condition
addLabelForNextNode(conditionStart);
assert tree.getCondition() != null;
unbox(scan(tree.getCondition(), p));
ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit);
extendWithExtendedNode(cjump);
// Loop exit
addLabelForNextNode(loopExit);
breakTargetLC = oldBreakTargetLC;
continueTargetLC = oldContinueTargetLC;
return null;
}
@Override
public Node visitErroneous(ErroneousTree tree, Void p) {
throw new BugInCF("ErroneousTree is unexpected in AST to CFG translation: " + tree);
}
@Override
public Node visitExpressionStatement(ExpressionStatementTree tree, Void p) {
ExpressionTree exprTree = tree.getExpression();
scan(exprTree, p);
extendWithNode(new ExpressionStatementNode(exprTree));
return null;
}
@Override
public Node visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) {
// see JLS 14.14.2
Name parentLabel = getLabel(getCurrentPath());
Label conditionStart = new Label();
Label loopEntry = new Label();
Label loopExit = new Label();
// If the loop is a labeled statement, then its continue target is identical for continues
// with no label and continues with the loop's label.
Label updateStart;
if (parentLabel != null) {
updateStart = continueLabels.get(parentLabel);
} else {
updateStart = new Label();
}
LabelCell oldBreakTargetLC = breakTargetLC;
breakTargetLC = new LabelCell(loopExit);
LabelCell oldContinueTargetLC = continueTargetLC;
continueTargetLC = new LabelCell(updateStart);
// Distinguish loops over Iterables from loops over arrays.
VariableTree variable = tree.getVariable();
VariableElement variableElement = TreeUtils.elementFromDeclaration(variable);
ExpressionTree expression = tree.getExpression();
StatementTree statement = tree.getStatement();
TypeMirror exprType = TreeUtils.typeOf(expression);
if (types.isSubtype(exprType, iterableType)) {
// Take the upper bound of a type variable or wildcard
exprType = TypesUtils.upperBound(exprType);
assert (exprType instanceof DeclaredType) : "an Iterable must be a DeclaredType";
DeclaredType declaredExprType = (DeclaredType) exprType;
declaredExprType.getTypeArguments();
MemberSelectTree iteratorSelect = treeBuilder.buildIteratorMethodAccess(expression);
handleArtificialTree(iteratorSelect);
MethodInvocationTree iteratorCall = treeBuilder.buildMethodInvocation(iteratorSelect);
handleArtificialTree(iteratorCall);
VariableTree iteratorVariable =
createEnhancedForLoopIteratorVariable(iteratorCall, variableElement);
handleArtificialTree(iteratorVariable);
VariableDeclarationNode iteratorVariableDecl =
new VariableDeclarationNode(iteratorVariable);
iteratorVariableDecl.setInSource(false);
extendWithNode(iteratorVariableDecl);
Node expressionNode = scan(expression, p);
MethodAccessNode iteratorAccessNode =
new MethodAccessNode(iteratorSelect, expressionNode);
iteratorAccessNode.setInSource(false);
extendWithNode(iteratorAccessNode);
MethodInvocationNode iteratorCallNode =
new MethodInvocationNode(
iteratorCall,
iteratorAccessNode,
Collections.emptyList(),
getCurrentPath());
iteratorCallNode.setInSource(false);
extendWithNode(iteratorCallNode);
translateAssignment(
iteratorVariable, new LocalVariableNode(iteratorVariable), iteratorCallNode);
// Test the loop ending condition
addLabelForNextNode(conditionStart);
IdentifierTree iteratorUse1 = treeBuilder.buildVariableUse(iteratorVariable);
handleArtificialTree(iteratorUse1);
LocalVariableNode iteratorReceiverNode = new LocalVariableNode(iteratorUse1);
iteratorReceiverNode.setInSource(false);
extendWithNode(iteratorReceiverNode);
MemberSelectTree hasNextSelect = treeBuilder.buildHasNextMethodAccess(iteratorUse1);
handleArtificialTree(hasNextSelect);
MethodAccessNode hasNextAccessNode =
new MethodAccessNode(hasNextSelect, iteratorReceiverNode);
hasNextAccessNode.setInSource(false);
extendWithNode(hasNextAccessNode);
MethodInvocationTree hasNextCall = treeBuilder.buildMethodInvocation(hasNextSelect);
handleArtificialTree(hasNextCall);
MethodInvocationNode hasNextCallNode =
new MethodInvocationNode(
hasNextCall,
hasNextAccessNode,
Collections.emptyList(),
getCurrentPath());
hasNextCallNode.setInSource(false);
extendWithNode(hasNextCallNode);
extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit));
// Loop body, starting with declaration of the loop iteration variable
addLabelForNextNode(loopEntry);
extendWithNode(new VariableDeclarationNode(variable));
IdentifierTree iteratorUse2 = treeBuilder.buildVariableUse(iteratorVariable);
handleArtificialTree(iteratorUse2);
LocalVariableNode iteratorReceiverNode2 = new LocalVariableNode(iteratorUse2);
iteratorReceiverNode2.setInSource(false);
extendWithNode(iteratorReceiverNode2);
MemberSelectTree nextSelect = treeBuilder.buildNextMethodAccess(iteratorUse2);
handleArtificialTree(nextSelect);
MethodAccessNode nextAccessNode =
new MethodAccessNode(nextSelect, iteratorReceiverNode2);
nextAccessNode.setInSource(false);
extendWithNode(nextAccessNode);
MethodInvocationTree nextCall = treeBuilder.buildMethodInvocation(nextSelect);
handleArtificialTree(nextCall);
MethodInvocationNode nextCallNode =
new MethodInvocationNode(
nextCall, nextAccessNode, Collections.emptyList(), getCurrentPath());
// If the type of iteratorVariable is a capture, its type tree may be missing
// annotations, so save the expression in the node so that the full type can be
// found later.
nextCallNode.setIterableExpression(expression);
nextCallNode.setInSource(false);
extendWithNode(nextCallNode);
AssignmentNode assignNode =
translateAssignment(variable, new LocalVariableNode(variable), nextCall);
// translateAssignment() scans variable and creates new nodes, so set the expression
// there, too.
((MethodInvocationNode) assignNode.getExpression()).setIterableExpression(expression);
assert statement != null;
scan(statement, p);
// Loop back edge
addLabelForNextNode(updateStart);
extendWithExtendedNode(new UnconditionalJump(conditionStart));
} else {
// TODO: Shift any labels after the initialization of the
// temporary array variable.
VariableTree arrayVariable =
createEnhancedForLoopArrayVariable(expression, variableElement);
handleArtificialTree(arrayVariable);
VariableDeclarationNode arrayVariableNode = new VariableDeclarationNode(arrayVariable);
arrayVariableNode.setInSource(false);
extendWithNode(arrayVariableNode);
Node expressionNode = scan(expression, p);
translateAssignment(
arrayVariable, new LocalVariableNode(arrayVariable), expressionNode);
// Declare and initialize the loop index variable
TypeMirror intType = types.getPrimitiveType(TypeKind.INT);
LiteralTree zero = treeBuilder.buildLiteral(Integer.valueOf(0));
handleArtificialTree(zero);
VariableTree indexVariable =
treeBuilder.buildVariableDecl(
intType,
uniqueName("index"),
variableElement.getEnclosingElement(),
zero);
handleArtificialTree(indexVariable);
VariableDeclarationNode indexVariableNode = new VariableDeclarationNode(indexVariable);
indexVariableNode.setInSource(false);
extendWithNode(indexVariableNode);
IntegerLiteralNode zeroNode = new IntegerLiteralNode(zero);
extendWithNode(zeroNode);
translateAssignment(indexVariable, new LocalVariableNode(indexVariable), zeroNode);
// Compare index to array length
addLabelForNextNode(conditionStart);
IdentifierTree indexUse1 = treeBuilder.buildVariableUse(indexVariable);
handleArtificialTree(indexUse1);
LocalVariableNode indexNode1 = new LocalVariableNode(indexUse1);
indexNode1.setInSource(false);
extendWithNode(indexNode1);
IdentifierTree arrayUse1 = treeBuilder.buildVariableUse(arrayVariable);
handleArtificialTree(arrayUse1);
LocalVariableNode arrayNode1 = new LocalVariableNode(arrayUse1);
extendWithNode(arrayNode1);
MemberSelectTree lengthSelect = treeBuilder.buildArrayLengthAccess(arrayUse1);
handleArtificialTree(lengthSelect);
FieldAccessNode lengthAccessNode = new FieldAccessNode(lengthSelect, arrayNode1);
lengthAccessNode.setInSource(false);
extendWithNode(lengthAccessNode);
BinaryTree lessThan = treeBuilder.buildLessThan(indexUse1, lengthSelect);
handleArtificialTree(lessThan);
LessThanNode lessThanNode = new LessThanNode(lessThan, indexNode1, lengthAccessNode);
lessThanNode.setInSource(false);
extendWithNode(lessThanNode);
extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit));
// Loop body, starting with declaration of the loop iteration variable
addLabelForNextNode(loopEntry);
extendWithNode(new VariableDeclarationNode(variable));
IdentifierTree arrayUse2 = treeBuilder.buildVariableUse(arrayVariable);
handleArtificialTree(arrayUse2);
LocalVariableNode arrayNode2 = new LocalVariableNode(arrayUse2);
arrayNode2.setInSource(false);
extendWithNode(arrayNode2);
IdentifierTree indexUse2 = treeBuilder.buildVariableUse(indexVariable);
handleArtificialTree(indexUse2);
LocalVariableNode indexNode2 = new LocalVariableNode(indexUse2);
indexNode2.setInSource(false);
extendWithNode(indexNode2);
ArrayAccessTree arrayAccess = treeBuilder.buildArrayAccess(arrayUse2, indexUse2);
handleArtificialTree(arrayAccess);
ArrayAccessNode arrayAccessNode =
new ArrayAccessNode(arrayAccess, arrayNode2, indexNode2);
arrayAccessNode.setArrayExpression(expression);
arrayAccessNode.setInSource(false);
extendWithNode(arrayAccessNode);
AssignmentNode arrayAccessAssignNode =
translateAssignment(variable, new LocalVariableNode(variable), arrayAccessNode);
extendWithNodeWithException(arrayAccessNode, nullPointerExceptionType);
// translateAssignment() scans variable and creates new nodes, so set the expression
// there, too.
Node arrayAccessAssignNodeExpr = arrayAccessAssignNode.getExpression();
if (arrayAccessAssignNodeExpr instanceof ArrayAccessNode) {
((ArrayAccessNode) arrayAccessAssignNodeExpr).setArrayExpression(expression);
} else if (arrayAccessAssignNodeExpr instanceof MethodInvocationNode) {
// If the array component type is a primitive, there may be a boxing or unboxing
// conversion. Treat that as an iterator.
MethodInvocationNode boxingNode = (MethodInvocationNode) arrayAccessAssignNodeExpr;
boxingNode.setIterableExpression(expression);
}
assert statement != null;
scan(statement, p);
// Loop back edge
addLabelForNextNode(updateStart);
IdentifierTree indexUse3 = treeBuilder.buildVariableUse(indexVariable);
handleArtificialTree(indexUse3);
LocalVariableNode indexNode3 = new LocalVariableNode(indexUse3);
indexNode3.setInSource(false);
extendWithNode(indexNode3);
LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1));
handleArtificialTree(oneTree);
Node one = new IntegerLiteralNode(oneTree);
one.setInSource(false);
extendWithNode(one);
BinaryTree addOneTree =
treeBuilder.buildBinary(intType, Tree.Kind.PLUS, indexUse3, oneTree);
handleArtificialTree(addOneTree);
Node addOneNode = new NumericalAdditionNode(addOneTree, indexNode3, one);
addOneNode.setInSource(false);
extendWithNode(addOneNode);
AssignmentTree assignTree = treeBuilder.buildAssignment(indexUse3, addOneTree);
handleArtificialTree(assignTree);
Node assignNode = new AssignmentNode(assignTree, indexNode3, addOneNode);
assignNode.setInSource(false);
extendWithNode(assignNode);
extendWithExtendedNode(new UnconditionalJump(conditionStart));
}
// Loop exit
addLabelForNextNode(loopExit);
breakTargetLC = oldBreakTargetLC;
continueTargetLC = oldContinueTargetLC;
return null;
}
protected VariableTree createEnhancedForLoopIteratorVariable(
MethodInvocationTree iteratorCall, VariableElement variableElement) {
TypeMirror iteratorType = TreeUtils.typeOf(iteratorCall);
// Declare and initialize a new, unique iterator variable
VariableTree iteratorVariable =
treeBuilder.buildVariableDecl(
iteratorType, // annotatedIteratorTypeTree,
uniqueName("iter"),
variableElement.getEnclosingElement(),
iteratorCall);
return iteratorVariable;
}
protected VariableTree createEnhancedForLoopArrayVariable(
ExpressionTree expression, VariableElement variableElement) {
TypeMirror arrayType = TreeUtils.typeOf(expression);
// Declare and initialize a temporary array variable
VariableTree arrayVariable =
treeBuilder.buildVariableDecl(
arrayType,
uniqueName("array"),
variableElement.getEnclosingElement(),
expression);
return arrayVariable;
}
@Override
public Node visitForLoop(ForLoopTree tree, Void p) {
Name parentLabel = getLabel(getCurrentPath());
Label conditionStart = new Label();
Label loopEntry = new Label();
Label loopExit = new Label();
// If the loop is a labeled statement, then its continue target is identical for continues
// with no label and continues with the loop's label.
Label updateStart;
if (parentLabel != null) {
updateStart = continueLabels.get(parentLabel);
} else {
updateStart = new Label();
}
LabelCell oldBreakTargetLC = breakTargetLC;
breakTargetLC = new LabelCell(loopExit);
LabelCell oldContinueTargetLC = continueTargetLC;
continueTargetLC = new LabelCell(updateStart);
// Initializer
for (StatementTree init : tree.getInitializer()) {
scan(init, p);
}
// Condition
addLabelForNextNode(conditionStart);
if (tree.getCondition() != null) {
unbox(scan(tree.getCondition(), p));
ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit);
extendWithExtendedNode(cjump);
}
// Loop body
addLabelForNextNode(loopEntry);
assert tree.getStatement() != null;
scan(tree.getStatement(), p);
// Update
addLabelForNextNode(updateStart);
for (ExpressionStatementTree update : tree.getUpdate()) {
scan(update, p);
}
extendWithExtendedNode(new UnconditionalJump(conditionStart));
// Loop exit
addLabelForNextNode(loopExit);
breakTargetLC = oldBreakTargetLC;
continueTargetLC = oldContinueTargetLC;
return null;
}
@Override
public Node visitIdentifier(IdentifierTree tree, Void p) {
Node node;
if (TreeUtils.isFieldAccess(tree)) {
Node receiver = getReceiver(tree);
node = new FieldAccessNode(tree, receiver);
} else {
Element element = TreeUtils.elementFromUse(tree);
switch (element.getKind()) {
case FIELD:
// Note that "this"/"super" is a field, but not a field access.
if (element.getSimpleName().contentEquals("this")) {
node = new ExplicitThisNode(tree);
} else {
node = new SuperNode(tree);
}
break;
case EXCEPTION_PARAMETER:
case LOCAL_VARIABLE:
case RESOURCE_VARIABLE:
case PARAMETER:
node = new LocalVariableNode(tree);
break;
case PACKAGE:
node = new PackageNameNode(tree);
break;
default:
if (ElementUtils.isTypeDeclaration(element)) {
node = new ClassNameNode(tree);
break;
} else if (ElementUtils.isBindingVariable(element)) {
// Note: BINDING_VARIABLE should be added as a direct case above when
// instanceof pattern matching and Java15 are supported.
node = new LocalVariableNode(tree);
break;
}
throw new BugInCF("bad element kind " + element.getKind());
}
}
if (node instanceof ClassNameNode) {
extendWithClassNameNode((ClassNameNode) node);
} else {
extendWithNode(node);
}
return node;
}
@Override
public Node visitIf(IfTree tree, Void p) {
// all necessary labels
Label thenEntry = new Label();
Label elseEntry = new Label();
Label endIf = new Label();
// basic block for the condition
unbox(scan(tree.getCondition(), p));
ConditionalJump cjump = new ConditionalJump(thenEntry, elseEntry);
extendWithExtendedNode(cjump);
// then branch
addLabelForNextNode(thenEntry);
StatementTree thenStatement = tree.getThenStatement();
scan(thenStatement, p);
extendWithExtendedNode(new UnconditionalJump(endIf));
// else branch
addLabelForNextNode(elseEntry);
StatementTree elseStatement = tree.getElseStatement();
if (elseStatement != null) {
scan(elseStatement, p);
}
// label the end of the if statement
addLabelForNextNode(endIf);
return null;
}
@Override
public Node visitImport(ImportTree tree, Void p) {
throw new BugInCF("ImportTree is unexpected in AST to CFG translation: " + tree);
}
@Override
public Node visitArrayAccess(ArrayAccessTree tree, Void p) {
Node array = scan(tree.getExpression(), p);
Node index = unaryNumericPromotion(scan(tree.getIndex(), p));
Node arrayAccess = new ArrayAccessNode(tree, array, index);
extendWithNode(arrayAccess);
extendWithNodeWithException(arrayAccess, arrayIndexOutOfBoundsExceptionType);
extendWithNodeWithException(arrayAccess, nullPointerExceptionType);
return arrayAccess;
}
@Override
public Node visitLabeledStatement(LabeledStatementTree tree, Void p) {
// This method can set the break target after generating all Nodes in the contained
// statement, but it can't set the continue target, which may be in the middle of a
// sequence of nodes. Labeled loops must look up and use the continue Labels.
Name labelName = tree.getLabel();
Label breakLabel = new Label(labelName + "_break");
Label continueLabel = new Label(labelName + "_continue");
breakLabels.put(labelName, breakLabel);
continueLabels.put(labelName, continueLabel);
scan(tree.getStatement(), p);
addLabelForNextNode(breakLabel);
breakLabels.remove(labelName);
continueLabels.remove(labelName);
return null;
}
@Override
public Node visitLiteral(LiteralTree tree, Void p) {
Node r = null;
switch (tree.getKind()) {
case BOOLEAN_LITERAL:
r = new BooleanLiteralNode(tree);
break;
case CHAR_LITERAL:
r = new CharacterLiteralNode(tree);
break;
case DOUBLE_LITERAL:
r = new DoubleLiteralNode(tree);
break;
case FLOAT_LITERAL:
r = new FloatLiteralNode(tree);
break;
case INT_LITERAL:
r = new IntegerLiteralNode(tree);
break;
case LONG_LITERAL:
r = new LongLiteralNode(tree);
break;
case NULL_LITERAL:
r = new NullLiteralNode(tree);
break;
case STRING_LITERAL:
r = new StringLiteralNode(tree);
break;
default:
throw new BugInCF("unexpected literal tree: " + tree);
}
assert r != null : "unexpected literal tree";
extendWithNode(r);
return r;
}
@Override
public Node visitMethod(MethodTree tree, Void p) {
throw new BugInCF("MethodTree is unexpected in AST to CFG translation");
}
@Override
public Node visitModifiers(ModifiersTree tree, Void p) {
throw new BugInCF("ModifiersTree is unexpected in AST to CFG translation");
}
@Override
public Node visitNewArray(NewArrayTree tree, Void p) {
// see JLS 15.10
ArrayType type = (ArrayType) TreeUtils.typeOf(tree);
TypeMirror elemType = type.getComponentType();
List extends ExpressionTree> dimensions = tree.getDimensions();
List extends ExpressionTree> initializers = tree.getInitializers();
assert dimensions != null;
List dimensionNodes =
CollectionsPlume.mapList(dim -> unaryNumericPromotion(scan(dim, p)), dimensions);
List initializerNodes;
if (initializers == null) {
initializerNodes = Collections.emptyList();
} else {
initializerNodes =
CollectionsPlume.mapList(
init -> assignConvert(scan(init, p), elemType), initializers);
}
Node node = new ArrayCreationNode(tree, type, dimensionNodes, initializerNodes);
extendWithNodeWithExceptions(node, newArrayExceptionTypes);
return node;
}
@Override
public Node visitNewClass(NewClassTree tree, Void p) {
// see JLS 15.9
DeclaredType classType = (DeclaredType) TreeUtils.typeOf(tree);
TypeMirror enclosingType = classType.getEnclosingType();
Tree enclosingExpr = tree.getEnclosingExpression();
Node enclosingExprNode;
if (enclosingExpr != null) {
enclosingExprNode = scan(enclosingExpr, p);
} else if (enclosingType.getKind() == TypeKind.DECLARED) {
// This is an inner class (instance nested class).
// As there is no explicit enclosing expression, create a node for the implicit this
// argument.
enclosingExprNode = new ImplicitThisNode(enclosingType);
extendWithNode(enclosingExprNode);
} else {
// For static nested classes, the kind would be Typekind.None.
enclosingExprNode = null;
}
// Convert constructor arguments
ExecutableElement constructor = TreeUtils.elementFromUse(tree);
List extends ExpressionTree> actualExprs = tree.getArguments();
List arguments =
convertCallArguments(
tree, constructor, TreeUtils.typeFromUse(tree), actualExprs, tree);
// TODO: for anonymous classes, don't use the identifier alone.
// See https://github.com/typetools/checker-framework/issues/890 .
Node constructorNode = scan(tree.getIdentifier(), p);
// Handle anonymous classes in visitClass.
// Note that getClassBody() and therefore classbody can be null.
ClassDeclarationNode classbody = (ClassDeclarationNode) scan(tree.getClassBody(), p);
Node node =
new ObjectCreationNode(
tree, enclosingExprNode, constructorNode, arguments, classbody);
List extends TypeMirror> thrownTypes = constructor.getThrownTypes();
Set thrownSet =
ArraySet.newArraySetOrLinkedHashSet(
thrownTypes.size() + uncheckedExceptionTypes.size());
// Add exceptions explicitly mentioned in the throws clause.
thrownSet.addAll(thrownTypes);
// Add types to account for unchecked exceptions
thrownSet.addAll(uncheckedExceptionTypes);
extendWithNodeWithExceptions(node, thrownSet);
return node;
}
/**
* Maps a {@code Tree} to its directly enclosing {@code ParenthesizedTree} if one exists.
*
* This map is used by {@link CFGTranslationPhaseOne#addToLookupMap(Node)} to associate a
* {@code ParenthesizedTree} with the dataflow {@code Node} that was used during inference. This
* map is necessary because dataflow does not create a {@code Node} for a {@code
* ParenthesizedTree}.
*/
private final Map parenMapping = new HashMap<>();
@Override
public Node visitParenthesized(ParenthesizedTree tree, Void p) {
parenMapping.put(tree.getExpression(), tree);
return scan(tree.getExpression(), p);
}
@Override
public Node visitReturn(ReturnTree tree, Void p) {
ExpressionTree ret = tree.getExpression();
// TODO: also have a return-node if nothing is returned
ReturnNode result = null;
if (ret != null) {
Node node = scan(ret, p);
result = new ReturnNode(tree, node, env.getTypeUtils());
returnNodes.add(result);
extendWithNode(result);
}
extendWithExtendedNode(new UnconditionalJump(this.returnTargetLC.accessLabel()));
return result;
}
@Override
public Node visitMemberSelect(MemberSelectTree tree, Void p) {
Node expr = scan(tree.getExpression(), p);
if (!TreeUtils.isFieldAccess(tree)) {
// Could be a selector of a class or package
Element element = TreeUtils.elementFromUse(tree);
if (ElementUtils.isTypeElement(element)) {
ClassNameNode result = new ClassNameNode(tree, expr);
extendWithClassNameNode(result);
return result;
} else if (element.getKind() == ElementKind.PACKAGE) {
Node result = new PackageNameNode(tree, (PackageNameNode) expr);
extendWithNode(result);
return result;
} else {
throw new BugInCF("Unexpected element kind: " + element.getKind());
}
}
Node node = new FieldAccessNode(tree, expr);
Element element = TreeUtils.elementFromUse(tree);
if (ElementUtils.isStatic(element) || expr instanceof ThisNode) {
// No NullPointerException can be thrown, use normal node
extendWithNode(node);
} else {
extendWithNodeWithException(node, nullPointerExceptionType);
}
return node;
}
@Override
public Node visitEmptyStatement(EmptyStatementTree tree, Void p) {
return null;
}
@Override
public Node visitSynchronized(SynchronizedTree tree, Void p) {
// see JLS 14.19
Node synchronizedExpr = scan(tree.getExpression(), p);
SynchronizedNode synchronizedStartNode =
new SynchronizedNode(tree, synchronizedExpr, true, env.getTypeUtils());
extendWithNode(synchronizedStartNode);
scan(tree.getBlock(), p);
SynchronizedNode synchronizedEndNode =
new SynchronizedNode(tree, synchronizedExpr, false, env.getTypeUtils());
extendWithNode(synchronizedEndNode);
return null;
}
@Override
public Node visitThrow(ThrowTree tree, Void p) {
Node expression = scan(tree.getExpression(), p);
TypeMirror exception = expression.getType();
ThrowNode throwsNode = new ThrowNode(tree, expression, env.getTypeUtils());
NodeWithExceptionsHolder exNode = extendWithNodeWithException(throwsNode, exception);
exNode.setTerminatesExecution(true);
return throwsNode;
}
@Override
public Node visitCompilationUnit(CompilationUnitTree tree, Void p) {
throw new BugInCF("CompilationUnitTree is unexpected in AST to CFG translation");
}
/**
* Return the first argument if it is non-null, otherwise return the second argument. Throws an
* exception if both arguments are null.
*
* @param the type of the arguments
* @param first a reference
* @param second a reference
* @return the first argument that is non-null
*/
private static A firstNonNull(A first, A second) {
if (first != null) {
return first;
} else if (second != null) {
return second;
} else {
throw new NullPointerException();
}
}
@Override
public Node visitTry(TryTree tree, Void p) {
List extends CatchTree> catches = tree.getCatches();
BlockTree finallyBlock = tree.getFinallyBlock();
extendWithNode(
new MarkerNode(
tree,
"start of try statement #" + TreeUtils.treeUids.get(tree),
env.getTypeUtils()));
List> catchLabels =
CollectionsPlume.mapList(
(CatchTree c) ->
IPair.of(TreeUtils.typeOf(c.getParameter().getType()), new Label()),
catches);
// Store return/break/continue labels, just in case we need them for a finally block.
LabelCell oldReturnTargetLC = returnTargetLC;
LabelCell oldBreakTargetLC = breakTargetLC;
Map oldBreakLabels = breakLabels;
LabelCell oldContinueTargetLC = continueTargetLC;
Map oldContinueLabels = continueLabels;
Label finallyLabel = null;
Label exceptionalFinallyLabel = null;
if (finallyBlock != null) {
finallyLabel = new Label();
exceptionalFinallyLabel = new Label();
tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel));
returnTargetLC = new LabelCell();
breakTargetLC = new LabelCell();
breakLabels = new TryFinallyScopeMap();
continueTargetLC = new LabelCell();
continueLabels = new TryFinallyScopeMap();
}
Label doneLabel = new Label();
tryStack.pushFrame(new TryCatchFrame(types, catchLabels));
extendWithNode(
new MarkerNode(
tree,
"start of try block #" + TreeUtils.treeUids.get(tree),
env.getTypeUtils()));
handleTryResourcesAndBlock(tree, p, tree.getResources());
extendWithNode(
new MarkerNode(
tree,
"end of try block #" + TreeUtils.treeUids.get(tree),
env.getTypeUtils()));
extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel)));
// This pops the try-catch frame
tryStack.popFrame();
int catchIndex = 0;
for (CatchTree c : catches) {
addLabelForNextNode(catchLabels.get(catchIndex).second);
TypeMirror catchType = TreeUtils.typeOf(c.getParameter().getType());
extendWithNode(new CatchMarkerNode(tree, "start", catchType, env.getTypeUtils()));
scan(c, p);
extendWithNode(new CatchMarkerNode(tree, "end", catchType, env.getTypeUtils()));
catchIndex++;
extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel)));
}
if (finallyLabel != null) {
handleFinally(
tree,
doneLabel,
finallyLabel,
exceptionalFinallyLabel,
() -> scan(finallyBlock, p),
oldReturnTargetLC,
oldBreakTargetLC,
oldBreakLabels,
oldContinueTargetLC,
oldContinueLabels);
}
addLabelForNextNode(doneLabel);
return null;
}
/**
* A recursive helper method to handle the resource declarations (if any) in a {@link TryTree}
* and its main block. If the {@code resources} list is empty, the method scans the main block
* of the try statement and returns. Otherwise, the first resource declaration in {@code
* resources} is desugared, following the logic in JLS 14.20.3.1. A resource declaration
* r is desugared by adding the nodes for r itself to the CFG, followed by a
* synthetic nested {@code try} block and {@code finally} block. The synthetic {@code try} block
* contains any remaining resource declarations and the original try block (handled via
* recursion). The synthetic {@code finally} block contains a call to {@code close} for
* r , guaranteeing that on every path through the CFG, r is closed.
*
* @param tryTree the original try tree (with 0 or more resources) from the AST
* @param p the value to pass to calls to {@code scan}
* @param resources the remaining resource declarations to handle
*/
private void handleTryResourcesAndBlock(
TryTree tryTree, Void p, List extends Tree> resources) {
if (resources.isEmpty()) {
// Either `tryTree` was not a try-with-resources, or this method was called recursively
// and all the resources have been handled. Just scan the main try block.
scan(tryTree.getBlock(), p);
return;
}
// Handle the first resource declaration in the list. The rest will be handled by a
// recursive call.
Tree resourceDeclarationTree = resources.get(0);
extendWithNode(
new MarkerNode(
resourceDeclarationTree,
"start of try for resource #"
+ TreeUtils.treeUids.get(resourceDeclarationTree),
env.getTypeUtils()));
// Store return/break/continue labels. Generating a synthetic finally block for closing the
// resource requires creating fresh return/break/continue labels and then restoring the old
// labels afterward.
LabelCell oldReturnTargetLC = returnTargetLC;
LabelCell oldBreakTargetLC = breakTargetLC;
Map oldBreakLabels = breakLabels;
LabelCell oldContinueTargetLC = continueTargetLC;
Map oldContinueLabels = continueLabels;
// Add nodes for the resource declaration to the CFG. NOTE: it is critical to add these
// nodes *before* pushing a TryFinallyFrame for the finally block that will close the
// resource.
// If any exception occurs due to code within the resource declaration, the corresponding
// variable or field is *not* automatically closed (as it was never assigned a value).
Node resourceCloseNode = scan(resourceDeclarationTree, p);
// Now, set things up for our synthetic finally block that closes the resource.
Label doneLabel = new Label();
Label finallyLabel = new Label();
Label exceptionalFinallyLabel = new Label();
tryStack.pushFrame(new TryFinallyFrame(exceptionalFinallyLabel));
returnTargetLC = new LabelCell();
breakTargetLC = new LabelCell();
breakLabels = new TryFinallyScopeMap();
continueTargetLC = new LabelCell();
continueLabels = new TryFinallyScopeMap();
extendWithNode(
new MarkerNode(
resourceDeclarationTree,
"start of try block for resource #"
+ TreeUtils.treeUids.get(resourceDeclarationTree),
env.getTypeUtils()));
// Recursively handle any remaining resource declarations and the main block of the try
handleTryResourcesAndBlock(tryTree, p, resources.subList(1, resources.size()));
extendWithNode(
new MarkerNode(
resourceDeclarationTree,
"end of try block for resource #"
+ TreeUtils.treeUids.get(resourceDeclarationTree),
env.getTypeUtils()));
extendWithExtendedNode(new UnconditionalJump(finallyLabel));
// Generate the finally block that closes the resource
handleFinally(
resourceDeclarationTree,
doneLabel,
finallyLabel,
exceptionalFinallyLabel,
() -> addCloseCallForResource(resourceDeclarationTree, resourceCloseNode),
oldReturnTargetLC,
oldBreakTargetLC,
oldBreakLabels,
oldContinueTargetLC,
oldContinueLabels);
addLabelForNextNode(doneLabel);
}
/**
* Adds a synthetic {@code close} call to the CFG to close some resource variable declared or
* used in a try-with-resources.
*
* @param resourceDeclarationTree the resource declaration
* @param resourceToCloseNode node represented the variable or field on which {@code close}
* should be invoked
*/
private void addCloseCallForResource(Tree resourceDeclarationTree, Node resourceToCloseNode) {
Tree receiverTree = resourceDeclarationTree;
if (receiverTree instanceof VariableTree) {
receiverTree = treeBuilder.buildVariableUse((VariableTree) receiverTree);
handleArtificialTree(receiverTree);
}
MemberSelectTree closeSelect =
treeBuilder.buildCloseMethodAccess((ExpressionTree) receiverTree);
handleArtificialTree(closeSelect);
MethodInvocationTree closeCall = treeBuilder.buildMethodInvocation(closeSelect);
handleArtificialTree(closeCall);
Node receiverNode = resourceToCloseNode;
if (receiverNode instanceof AssignmentNode) {
// variable declaration; use the LHS
receiverNode = ((AssignmentNode) resourceToCloseNode).getTarget();
}
// TODO do we need to insert some kind of node representing a use of receiverNode
// (which can be either a LocalVariableNode or a FieldAccessNode)?
MethodAccessNode closeAccessNode = new MethodAccessNode(closeSelect, receiverNode);
closeAccessNode.setInSource(false);
extendWithNode(closeAccessNode);
MethodInvocationNode closeCallNode =
new MethodInvocationNode(
closeCall, closeAccessNode, Collections.emptyList(), getCurrentPath());
closeCallNode.setInSource(false);
extendWithMethodInvocationNode(TreeUtils.elementFromUse(closeCall), closeCallNode);
}
/**
* Shared logic for CFG generation for a finally block. The block may correspond to a {@link
* TryTree} originally in the source code, or it may be a synthetic finally block used to model
* closing of a resource due to try-with-resources.
*
* @param markerTree tree to reference when creating {@link MarkerNode}s for the finally block
* @param doneLabel label for the normal successor of the try block (no exceptions, returns,
* breaks, or continues)
* @param finallyLabel label for the entry of the finally block for the normal case
* @param exceptionalFinallyLabel label for entry of the finally block for when the try block
* throws an exception
* @param finallyBlockCFGGenerator generates CFG nodes and edges for the finally block
* @param oldReturnTargetLC old return target label cell, which gets restored to {@link
* #returnTargetLC} while handling the finally block
* @param oldBreakTargetLC old break target label cell, which gets restored to {@link
* #breakTargetLC} while handling the finally block
* @param oldBreakLabels old break labels, which get restored to {@link #breakLabels} while
* handling the finally block
* @param oldContinueTargetLC old continue target label cell, which gets restored to {@link
* #continueTargetLC} while handling the finally block
* @param oldContinueLabels old continue labels, which get restored to {@link #continueLabels}
* while handling the finally block
*/
private void handleFinally(
Tree markerTree,
Label doneLabel,
Label finallyLabel,
Label exceptionalFinallyLabel,
Runnable finallyBlockCFGGenerator,
LabelCell oldReturnTargetLC,
LabelCell oldBreakTargetLC,
Map oldBreakLabels,
LabelCell oldContinueTargetLC,
Map oldContinueLabels) {
// Reset values before analyzing the finally block!
tryStack.popFrame();
{ // Scan 'finallyBlock' for only 'finallyLabel' (a successful path)
addLabelForNextNode(finallyLabel);
extendWithNode(
new MarkerNode(
markerTree,
"start of finally block #" + TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
finallyBlockCFGGenerator.run();
extendWithNode(
new MarkerNode(
markerTree,
"end of finally block #" + TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
extendWithExtendedNode(new UnconditionalJump(doneLabel));
}
if (hasExceptionalPath(exceptionalFinallyLabel)) {
// If an exceptional path exists, scan 'finallyBlock' for 'exceptionalFinallyLabel',
// and scan copied 'finallyBlock' for 'finallyLabel' (a successful path). If there
// is no successful path, it will be removed in later phase.
// TODO: Don't we need a separate finally block for each kind of exception?
addLabelForNextNode(exceptionalFinallyLabel);
extendWithNode(
new MarkerNode(
markerTree,
"start of finally block for Throwable #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
finallyBlockCFGGenerator.run();
NodeWithExceptionsHolder throwing =
extendWithNodeWithException(
new MarkerNode(
markerTree,
"end of finally block for Throwable #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()),
throwableType);
throwing.setTerminatesExecution(true);
}
if (returnTargetLC.wasAccessed()) {
addLabelForNextNode(returnTargetLC.peekLabel());
returnTargetLC = oldReturnTargetLC;
extendWithNode(
new MarkerNode(
markerTree,
"start of finally block for return #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
finallyBlockCFGGenerator.run();
extendWithNode(
new MarkerNode(
markerTree,
"end of finally block for return #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
extendWithExtendedNode(new UnconditionalJump(returnTargetLC.accessLabel()));
} else {
returnTargetLC = oldReturnTargetLC;
}
if (breakTargetLC.wasAccessed()) {
addLabelForNextNode(breakTargetLC.peekLabel());
breakTargetLC = oldBreakTargetLC;
extendWithNode(
new MarkerNode(
markerTree,
"start of finally block for break #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
finallyBlockCFGGenerator.run();
extendWithNode(
new MarkerNode(
markerTree,
"end of finally block for break #" + TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
extendWithExtendedNode(new UnconditionalJump(breakTargetLC.accessLabel()));
} else {
breakTargetLC = oldBreakTargetLC;
}
Map accessedBreakLabels =
((TryFinallyScopeMap) breakLabels).getAccessedNames();
if (!accessedBreakLabels.isEmpty()) {
breakLabels = oldBreakLabels;
for (Map.Entry access : accessedBreakLabels.entrySet()) {
addLabelForNextNode(access.getValue());
extendWithNode(
new MarkerNode(
markerTree,
"start of finally block for break label "
+ access.getKey()
+ " #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
finallyBlockCFGGenerator.run();
extendWithNode(
new MarkerNode(
markerTree,
"end of finally block for break label "
+ access.getKey()
+ " #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
extendWithExtendedNode(new UnconditionalJump(breakLabels.get(access.getKey())));
}
} else {
breakLabels = oldBreakLabels;
}
if (continueTargetLC.wasAccessed()) {
addLabelForNextNode(continueTargetLC.peekLabel());
continueTargetLC = oldContinueTargetLC;
extendWithNode(
new MarkerNode(
markerTree,
"start of finally block for continue #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
finallyBlockCFGGenerator.run();
extendWithNode(
new MarkerNode(
markerTree,
"end of finally block for continue #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
extendWithExtendedNode(new UnconditionalJump(continueTargetLC.accessLabel()));
} else {
continueTargetLC = oldContinueTargetLC;
}
Map accessedContinueLabels =
((TryFinallyScopeMap) continueLabels).getAccessedNames();
if (!accessedContinueLabels.isEmpty()) {
continueLabels = oldContinueLabels;
for (Map.Entry access : accessedContinueLabels.entrySet()) {
addLabelForNextNode(access.getValue());
extendWithNode(
new MarkerNode(
markerTree,
"start of finally block for continue label "
+ access.getKey()
+ " #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
finallyBlockCFGGenerator.run();
extendWithNode(
new MarkerNode(
markerTree,
"end of finally block for continue label "
+ access.getKey()
+ " #"
+ TreeUtils.treeUids.get(markerTree),
env.getTypeUtils()));
extendWithExtendedNode(new UnconditionalJump(continueLabels.get(access.getKey())));
}
} else {
continueLabels = oldContinueLabels;
}
}
/**
* Returns whether an exceptional node for {@code target} exists in {@link #nodeList} or not.
*
* @param target label for exception
* @return true when an exceptional node for {@code target} exists in {@link #nodeList}
*/
private boolean hasExceptionalPath(Label target) {
for (ExtendedNode node : nodeList) {
if (node instanceof NodeWithExceptionsHolder) {
NodeWithExceptionsHolder exceptionalNode = (NodeWithExceptionsHolder) node;
for (Set 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 BugInCF("UnionTypeTree is unexpected in AST to CFG translation: " + tree);
}
@Override
public Node visitArrayType(ArrayTypeTree tree, Void p) {
Node result = new ArrayTypeNode(tree, types);
extendWithNode(result);
return result;
}
@Override
public Node visitTypeCast(TypeCastTree tree, Void p) {
Node operand = scan(tree.getExpression(), p);
TypeMirror type = TreeUtils.typeOf(tree.getType());
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 BugInCF("TypeParameterTree is unexpected in AST to CFG translation");
}
@Override
public Node visitInstanceOf(InstanceOfTree tree, Void p) {
InstanceOfNode instanceOfNode;
Node operand = scan(tree.getExpression(), p);
Tree patternTree = InstanceOfUtils.getPattern(tree);
if (patternTree != null) {
Node pattern = scan(patternTree, p);
instanceOfNode = new InstanceOfNode(tree, operand, pattern, pattern.getType(), types);
} else {
TypeMirror refType = TreeUtils.typeOf(tree.getType());
instanceOfNode = new InstanceOfNode(tree, operand, refType, types);
}
extendWithNode(instanceOfNode);
return instanceOfNode;
}
@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 BugInCF("Unexpected unary tree kind: " + 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 == Tree.Kind.PREFIX_INCREMENT;
boolean isPostfix =
kind == Tree.Kind.POSTFIX_INCREMENT
|| kind == Tree.Kind.POSTFIX_DECREMENT;
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);
}
AssignmentNode unaryAssign =
createIncrementOrDecrementAssign(tree, expr, isIncrement, isPostfix);
if (!isPostfix) {
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 BugInCF("Unknown kind (" + kind + ") of unary expression: " + tree);
}
return result;
}
/**
* Create assignment node which represent increment or decrement.
*
* @param unaryTree increment or decrement tree
* @param expr expression node to be incremented or decremented
* @param isIncrement true when it's increment
* @param isPostfix true if {@code expr} is a postfix increment or decrement
* @return assignment node for corresponding increment or decrement
*/
private AssignmentNode createIncrementOrDecrementAssign(
UnaryTree unaryTree, Node expr, boolean isIncrement, boolean isPostfix) {
ExpressionTree exprTree = (ExpressionTree) expr.getTree();
TypeMirror exprType = expr.getType();
TypeMirror oneType = types.getPrimitiveType(TypeKind.INT);
TypeMirror promotedType = binaryPromotedType(exprType, oneType);
LiteralTree oneTree = treeBuilder.buildLiteral(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);
if (isPostfix) {
postfixTreeToCfgNodes.put(unaryTree, operTree);
}
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);
Tree target;
if (isPostfix) {
target = treeBuilder.buildAssignment(exprTree, (ExpressionTree) narrowed.getTree());
handleArtificialTree(target);
} else {
target = unaryTree;
}
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 = false;
if (getCurrentPath().getParentPath() != null) {
Tree.Kind kind =
TreeUtils.getKindRecordAsClass(getCurrentPath().getParentPath().getLeaf());
// CLASS includes records.
if (kind == Tree.Kind.CLASS || kind == Tree.Kind.INTERFACE || kind == Tree.Kind.ENUM) {
isField = true;
}
}
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();
}
LabelCell oldBreakTargetLC = breakTargetLC;
breakTargetLC = new LabelCell(loopExit);
LabelCell oldContinueTargetLC = continueTargetLC;
continueTargetLC = new LabelCell(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);
breakTargetLC = oldBreakTargetLC;
continueTargetLC = oldContinueTargetLC;
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
*/
protected 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.
*
* This can be used to handle system types that are not present. For example, in Java code
* that is translated to JavaScript using j2cl, the custom bootclasspath contains APIs that are
* emulated in JavaScript, so some types such as OutOfMemoryError are deliberately not present.
*
* @param clazz a class, which must have a canonical name
* @return the TypeMirror for the class, or {@code null} if the type is not present
*/
protected @Nullable TypeMirror maybeGetTypeMirror(Class> clazz) {
String name = clazz.getCanonicalName();
assert name != null : clazz + " does not have a canonical name";
TypeElement element = elements.getTypeElement(name);
if (element == null) {
return null;
}
return element.asType();
}
}