Files
codeql/java/ql/lib/semmle/code/java/ControlFlowGraph.qll
2025-09-12 13:38:21 +02:00

1778 lines
68 KiB
Plaintext

/**
* Provides classes and predicates for computing expression-level intra-procedural control flow graphs.
*
* The only API exported by this library are the toplevel classes `ControlFlow::Node`
* and its subclass `ConditionNode`, which wrap the successor relation and the
* concept of true- and false-successors of conditions. A cfg node may either be a
* statement, an expression, or an exit node for a callable, indicating that
* execution of the callable terminates.
*/
overlay[local?]
module;
/*
* The implementation is centered around the concept of a _completion_, which
* models how the execution of a statement or expression terminates.
* Completions are represented as an algebraic data type `Completion` defined in
* `Completion.qll`.
*
* The CFG is built by structural recursion over the AST. To achieve this the
* CFG edges related to a given AST node, `n`, is divided into three categories:
* 1. The in-going edge that points to the first CFG node to execute when the
* `n` is going to be executed.
* 2. The out-going edges for control-flow leaving `n` that are going to some
* other node in the surrounding context of `n`.
* 3. The edges that have both of their end-points entirely within the AST
* node and its children.
* The edges in (1) and (2) are inherently non-local and are therefore
* initially calculated as half-edges, that is, the single node, `k`, of the
* edge contained within `n`, by the predicates `k = first(n)` and
* `last(n, k, _)`, respectively. The edges in (3) can then be enumerated
* directly by the predicate `succ` by calling `first` and `last` recursively
* on the children of `n` and connecting the end-points. This yields the entire
* CFG, since all edges are in (3) for _some_ AST node.
*
* The third parameter of `last` is the completion, which is necessary to
* distinguish the out-going edges from `n`. Note that the completion changes
* as the calculation of `last` proceeds outward through the AST; for example,
* a `breakCompletion` is caught up by its surrounding loop and turned into a
* `normalCompletion`, or a `normalCompletion` proceeds outward through the end
* of a `finally` block and is turned into whatever completion was caught by
* the `finally`, or a `booleanCompletion(false, _)` occurs in a loop condition
* and is turned into a `normalCompletion` of the entire loop. When the edge is
* eventually connected we use the completion at that level of the AST as the
* label of the edge, thus creating an edge-labelled CFG.
*
* An important goal of the CFG is to get the order of side-effects correct.
* Most expressions can have side-effects and must therefore be modeled in the
* CFG in AST post-order. For example, a `MethodCall` evaluates its arguments
* before the call. Most statements don't have side-effects, but merely affect
* the control-flow and some could therefore be excluded from the CFG. However,
* as a design choice, all statements are included in the CFG and generally
* serve as their own entry-points, thus executing in some version of AST
* pre-order. A few notable exceptions are `ReturnStmt`, `ThrowStmt`,
* `SynchronizedStmt`, `ThisConstructorInvocationStmt`, and
* `SuperConstructorInvocationStmt`, which all have side-effects and therefore
* are modeled in side-effect order. Loop statement nodes are only passed on
* entry, after which control goes back and forth between body and loop
* condition.
*
* Some out-going edges from boolean expressions have a known value and in some
* contexts this affects the possible successors. For example, in `if(A || B)`
* a short-circuit edge that skips `B` must be true and can therefore only lead
* to the then-branch. If the `||` is modeled in post-order then this
* information is lost, and consequently it is better to model `||` and `&&` in
* pre-order. The conditional expression `? :` is also modeled in pre-order to
* achieve consistent CFGs for the equivalent `A && B` and `A ? B : false`.
* Finally, the logical negation is also modeled in pre-order to achieve
* consistent CFGs for the equivalent `!(A || B)` and `!A && !B`. The boolean
* value `b` is tracked with the completion `booleanCompletion(b, _)`.
*
* Note that the second parameter in a `booleanCompletion` isn't needed to
* calculate the CFG. It is, however, needed to track the value of the
* sub-expression. For example, this ensures that the false-successor of the
* `ConditionNode` `A` in `if(!(A && B))` can be correctly identified as the
* then-branch (even though this completion turns into a
* `booleanCompletion(true, _)` from the perspective of the `if`-node).
*
* As a final note, expressions that aren't actually executed in the usual
* sense are excluded from the CFG. This covers, for example, parentheses,
* l-values that aren't r-values as well, and expressions in `ConstCase`s.
* For example, the `x` in `x=3` is not in the CFG, but the `x` in `x+=3` is.
*/
import java
private import codeql.controlflow.SuccessorType
private import codeql.util.Boolean
private import Completion
private import controlflow.internal.Preconditions
private import controlflow.internal.SwitchCases
/** Provides the definition of control flow nodes. */
module ControlFlow {
private predicate hasControlFlow(Expr e) {
not exists(ConstCase cc | e = cc.getValue(_)) and
not e.getParent*() instanceof Annotation and
not e instanceof TypeAccess and
not e instanceof ArrayTypeAccess and
not e instanceof UnionTypeAccess and
not e instanceof IntersectionTypeAccess and
not e instanceof WildcardTypeAccess and
not exists(AssignExpr ae | ae.getDest() = e)
}
private newtype TNode =
TExprNode(Expr e) { hasControlFlow(e) } or
TStmtNode(Stmt s) or
TAnnotatedExitNode(Callable c, Boolean normal) { exists(c.getBody()) } or
TExitNode(Callable c) { exists(c.getBody()) } or
TAssertThrowNode(AssertStmt s)
/** A node in the expression-level control-flow graph. */
class Node extends TNode {
/** Gets an immediate successor of this node. */
Node getASuccessor() { result = succ(this) }
/** Gets an immediate predecessor of this node. */
Node getAPredecessor() { this = succ(result) }
/** Gets an exception successor of this node. */
Node getAnExceptionSuccessor() { result = succ(this, ThrowCompletion(_)) }
/** Gets a successor of this node that is neither an exception successor nor a jump (break, continue, return). */
Node getANormalSuccessor() {
result = succ(this, BooleanCompletion(_, _)) or
result = succ(this, NormalCompletion())
}
/** Gets an immediate successor of this node of a given type, if any. */
Node getASuccessor(SuccessorType t) {
result = branchSuccessor(this, t.(BooleanSuccessor).getValue())
or
exists(Completion completion |
result = succ(this, completion) and
not result = branchSuccessor(this, _)
|
completion = NormalCompletion() and t instanceof DirectSuccessor
or
completion = ReturnCompletion() and t instanceof ReturnSuccessor
or
completion = BreakCompletion(_) and t instanceof BreakSuccessor
or
completion = YieldCompletion(_) and t instanceof BreakSuccessor
or
completion = ContinueCompletion(_) and t instanceof ContinueSuccessor
or
completion = ThrowCompletion(_) and t instanceof ExceptionSuccessor
)
}
/** Gets the basic block that contains this node. */
BasicBlock getBasicBlock() { result.getANode() = this }
/** Gets the statement containing this node, if any. */
Stmt getEnclosingStmt() { none() }
/** Gets the immediately enclosing callable whose body contains this node. */
Callable getEnclosingCallable() { none() }
/** Gets the statement this `Node` corresponds to, if any. */
Stmt asStmt() { this = TStmtNode(result) }
/** Gets the expression this `Node` corresponds to, if any. */
Expr asExpr() { this = TExprNode(result) }
/** Gets the call this `Node` corresponds to, if any. */
Call asCall() {
result = this.asExpr() or
result = this.asStmt()
}
/** Gets a textual representation of this element. */
string toString() { none() }
/** Gets the source location for this element. */
Location getLocation() { none() }
/**
* Gets the most appropriate AST node for this control flow node, if any.
*/
ExprParent getAstNode() { none() }
}
/** A control-flow node that represents the evaluation of an expression. */
class ExprNode extends Node, TExprNode {
Expr e;
ExprNode() { this = TExprNode(e) }
override Stmt getEnclosingStmt() { result = e.getEnclosingStmt() }
override Callable getEnclosingCallable() { result = e.getEnclosingCallable() }
override ExprParent getAstNode() { result = e }
/** Gets a textual representation of this element. */
override string toString() { result = e.toString() }
/** Gets the source location for this element. */
override Location getLocation() { result = e.getLocation() }
}
/** A control-flow node that represents a statement. */
class StmtNode extends Node, TStmtNode {
Stmt s;
StmtNode() { this = TStmtNode(s) }
override Stmt getEnclosingStmt() { result = s }
override Callable getEnclosingCallable() { result = s.getEnclosingCallable() }
override ExprParent getAstNode() { result = s }
override string toString() { result = s.toString() }
override Location getLocation() { result = s.getLocation() }
}
/** A control flow node indicating the normal or exceptional termination of a callable. */
class AnnotatedExitNode extends Node, TAnnotatedExitNode {
Callable c;
boolean normal;
AnnotatedExitNode() { this = TAnnotatedExitNode(c, normal) }
override Callable getEnclosingCallable() { result = c }
override ExprParent getAstNode() { result = c }
/** Gets a textual representation of this element. */
override string toString() {
normal = true and result = "Normal Exit"
or
normal = false and result = "Exceptional Exit"
}
/** Gets the source location for this element. */
override Location getLocation() { result = c.getLocation() }
}
/** A control flow node indicating normal termination of a callable. */
class NormalExitNode extends AnnotatedExitNode {
NormalExitNode() { this = TAnnotatedExitNode(_, true) }
}
/** A control flow node indicating exceptional termination of a callable. */
class ExceptionalExitNode extends AnnotatedExitNode {
ExceptionalExitNode() { this = TAnnotatedExitNode(_, false) }
}
/** A control flow node indicating the termination of a callable. */
class ExitNode extends Node, TExitNode {
Callable c;
ExitNode() { this = TExitNode(c) }
override Callable getEnclosingCallable() { result = c }
override ExprParent getAstNode() { result = c }
/** Gets a textual representation of this element. */
override string toString() { result = "Exit" }
/** Gets the source location for this element. */
override Location getLocation() { result = c.getLocation() }
}
/** A control flow node indicating a failing assertion. */
class AssertThrowNode extends Node, TAssertThrowNode {
AssertStmt s;
AssertThrowNode() { this = TAssertThrowNode(s) }
override Stmt getEnclosingStmt() { result = s }
override Callable getEnclosingCallable() { result = s.getEnclosingCallable() }
override ExprParent getAstNode() { result = s }
/** Gets a textual representation of this element. */
override string toString() { result = "Assert Throw" }
/** Gets the source location for this element. */
override Location getLocation() { result = s.getLocation() }
}
}
class ControlFlowNode = ControlFlow::Node;
/** Gets the intra-procedural successor of `n`. */
private ControlFlowNode succ(ControlFlowNode n) { result = succ(n, _) }
cached
private module ControlFlowGraphImpl {
private import ControlFlow
private class AstNode extends ExprParent {
AstNode() { this instanceof Expr or this instanceof Stmt }
Stmt getEnclosingStmt() {
result = this or
result = this.(Expr).getEnclosingStmt()
}
Node getCfgNode() { result.asExpr() = this or result.asStmt() = this }
}
/**
* Gets a label that applies to this statement.
*/
private Label getLabel(Stmt s) {
exists(LabeledStmt l | s = l.getStmt() |
result = MkLabel(l.getLabel()) or
result = getLabel(l)
)
}
/**
* A throwable that's a (reflexive, transitive) supertype of an unchecked
* exception. Besides the unchecked exceptions themselves, this includes
* `java.lang.Throwable` and `java.lang.Exception`.
*/
private class UncheckedThrowableSuperType extends RefType {
UncheckedThrowableSuperType() {
this instanceof TypeThrowable or
this instanceof TypeException or
this instanceof UncheckedThrowableType
}
/**
* An unchecked throwable that is a subtype of this `UncheckedThrowableSuperType` and
* sits as high as possible in the type hierarchy. This is mostly unique except for
* `TypeThrowable` which results in both `TypeError` and `TypeRuntimeException`.
*/
UncheckedThrowableType getAnUncheckedSubtype() {
result = this
or
result instanceof TypeError and this instanceof TypeThrowable
or
result instanceof TypeRuntimeException and
(this instanceof TypeThrowable or this instanceof TypeException)
}
}
/**
* Bind `t` to an exception type that may be thrown during execution of `n`,
* either because `n` is a `throw` statement, or because it is a call
* that may throw an exception, or because it is a cast and a
* `ClassCastException` is expected, or because it is a Kotlin not-null check
* and a `NullPointerException` is expected.
*/
private predicate mayThrow(AstNode n, ThrowableType t) {
t = n.(ThrowStmt).getThrownExceptionType()
or
exists(Call c | c = n |
t = c.getCallee().getAThrownExceptionType() or
uncheckedExceptionFromCatch(n, t) or
uncheckedExceptionFromFinally(n, t) or
uncheckedExceptionFromMethod(c, t)
)
or
exists(CastExpr c | c = n |
t instanceof TypeClassCastException and
uncheckedExceptionFromCatch(n, t)
)
or
exists(NotNullExpr nn | nn = n |
t instanceof TypeNullPointerException and
uncheckedExceptionFromCatch(n, t)
)
}
private predicate methodMayThrow(Method m, ThrowableType t) {
exists(AstNode n |
t = n.(ThrowStmt).getThrownExceptionType() and
not n.(ThrowStmt).getParent() = any(Method m0).getBody()
or
uncheckedExceptionFromMethod(n, t)
|
n.getEnclosingStmt().getEnclosingCallable() = m and
not exists(TryStmt try |
exists(try.getACatchClause()) and try.getBlock() = n.getEnclosingStmt().getEnclosingStmt*()
)
)
}
/**
* Bind `t` to an unchecked exception that may occur in a precondition check or guard wrapper.
*/
private predicate uncheckedExceptionFromMethod(MethodCall ma, ThrowableType t) {
(methodCallChecksArgument(ma) or methodCallUnconditionallyThrows(ma)) and
(t instanceof TypeError or t instanceof TypeRuntimeException)
or
methodMayThrow(ma.getMethod().getSourceDeclaration(), t)
}
/**
* Bind `t` to an unchecked exception that may transfer control to a finally
* block inside which `n` is nested.
*/
private predicate uncheckedExceptionFromFinally(AstNode n, ThrowableType t) {
exists(TryStmt try |
n.getEnclosingStmt().getEnclosingStmt+() = try.getBlock() or
n.(Expr).getParent*() = try.getAResource()
|
exists(try.getFinally()) and
(t instanceof TypeError or t instanceof TypeRuntimeException)
)
}
/**
* Bind `t` to all unchecked exceptions that may be caught by some
* `try-catch` inside which `n` is nested.
*/
private predicate uncheckedExceptionFromCatch(AstNode n, ThrowableType t) {
exists(TryStmt try, UncheckedThrowableSuperType caught |
n.getEnclosingStmt().getEnclosingStmt+() = try.getBlock() or
n.(Expr).getParent*() = try.getAResource()
|
t = caught.getAnUncheckedSubtype() and
try.getACatchClause().getACaughtType() = caught
)
}
private ThrowableType actualAssertionError() {
result.hasQualifiedName("java.lang", "AssertionError")
}
private ThrowableType assertionError() {
result = actualAssertionError()
or
// In case `AssertionError` is not extracted, we use `Error` as a fallback.
not exists(actualAssertionError()) and
result.hasQualifiedName("java.lang", "Error")
}
/**
* Gets an exception type that may be thrown during execution of the
* body or the resources (if any) of `try`.
*/
private ThrowableType thrownInBody(TryStmt try) {
exists(AstNode n |
mayThrow(n, result)
or
n instanceof AssertStmt and result = assertionError()
|
n.getEnclosingStmt().getEnclosingStmt+() = try.getBlock() or
n.(Expr).getParent*() = try.getAResource()
)
}
/**
* Bind `thrown` to an exception type that may be thrown during execution
* of the body or the resource declarations of the `try` block to which
* `c` belongs, such that `c` definitely catches that exception (if no
* prior catch clause handles it).
*/
private predicate mustCatch(CatchClause c, ThrowableType thrown) {
thrown = thrownInBody(c.getTry()) and
hasDescendant(c.getACaughtType(), thrown)
}
/**
* Bind `thrown` to an exception type that may be thrown during execution
* of the body or the resource declarations of the `try` block to which
* `c` belongs, such that `c` may _not_ catch that exception.
*
* This predicate computes the complement of `mustCatch` over those
* exception types that are thrown in the body/resource declarations of
* the corresponding `try`.
*/
private predicate mayNotCatch(CatchClause c, ThrowableType thrown) {
thrown = thrownInBody(c.getTry()) and
not hasDescendant(c.getACaughtType(), thrown)
}
/**
* Bind `thrown` to an exception type that may be thrown during execution
* of the body or the resource declarations of the `try` block to which
* `c` belongs, such that `c` possibly catches that exception.
*/
private predicate mayCatch(CatchClause c, ThrowableType thrown) {
mustCatch(c, thrown)
or
mayNotCatch(c, thrown) and exists(c.getACaughtType().commonSubtype(thrown))
}
/**
* Given an exception type `thrown`, determine which catch clauses of
* `try` may possibly catch that exception.
*/
private CatchClause handlingCatchClause(TryStmt try, ThrowableType thrown) {
exists(int i | result = try.getCatchClause(i) |
mayCatch(result, thrown) and
not exists(int j | j < i | mustCatch(try.getCatchClause(j), thrown))
)
}
/**
* Boolean expressions that occur in a context in which their value affect control flow.
* That is, contexts where the control-flow edges depend on `value` given that `b` ends
* with a `booleanCompletion(value, _)`.
*/
private predicate inBooleanContext(AstNode b) {
exists(LogicExpr logexpr |
logexpr.(BinaryExpr).getLeftOperand() = b
or
logexpr.getAnOperand() = b and inBooleanContext(logexpr)
)
or
exists(ConditionalExpr condexpr |
condexpr.getCondition() = b
or
condexpr.getABranchExpr() = b and
inBooleanContext(condexpr)
)
or
exists(AssertStmt assertstmt | assertstmt.getExpr() = b)
or
exists(SwitchExpr switch |
inBooleanContext(switch) and
switch.getAResult() = b
)
or
exists(ConditionalStmt condstmt | condstmt.getCondition() = b)
or
exists(WhenBranch whenbranch | whenbranch.getCondition() = b)
or
exists(WhenExpr whenexpr |
inBooleanContext(whenexpr) and
whenexpr.getBranch(_).getAResult() = b
)
or
b = any(PatternCase pc).getGuard()
or
inBooleanContext(b.(ExprStmt).getExpr())
or
inBooleanContext(b.(StmtExpr).getStmt())
}
/**
* A virtual method with a unique implementation. That is, the method does not
* participate in overriding and there are no call targets that could dispatch
* to both this and another method.
*/
private class EffectivelyNonVirtualMethod extends SrcMethod {
EffectivelyNonVirtualMethod() {
exists(this.getBody()) and
this.isVirtual() and
not this = any(Method m).getASourceOverriddenMethod() and
not this.overrides(_) and
// guard against implicit overrides of default methods
not this.getAPossibleImplementationOfSrcMethod() != this and
// guard against interface implementations in inheriting subclasses
not exists(SrcMethod m |
1 < strictcount(m.getAPossibleImplementationOfSrcMethod()) and
this = m.getAPossibleImplementationOfSrcMethod()
) and
// UnsupportedOperationException could indicate that this is meant to be overridden
not exists(ClassInstanceExpr ex |
this.getBody().getLastStmt().(ThrowStmt).getExpr() = ex and
ex.getConstructedType().hasQualifiedName("java.lang", "UnsupportedOperationException")
) and
// an unused parameter could indicate that this is meant to be overridden
forall(Parameter p | p = this.getAParameter() | exists(p.getAnAccess()))
}
/** Gets a `MethodCall` that calls this method. */
MethodCall getAnAccess() { result.getMethod().getAPossibleImplementation() = this }
}
/** Holds if a call to `m` indicates that `m` is expected to return. */
private predicate expectedReturn(EffectivelyNonVirtualMethod m) {
exists(Stmt s, BlockStmt b |
m.getAnAccess().getEnclosingStmt() = s and
b.getAStmt() = s and
not b.getLastStmt() = s
)
}
/**
* Gets a non-overridable method that always throws an exception or calls `exit`.
*/
private Method nonReturningMethod() {
result instanceof MethodExit
or
not result.isOverridable() and
exists(BlockStmt body |
body = result.getBody() and
not exists(ReturnStmt ret | ret.getEnclosingCallable() = result)
|
not result.getReturnType() instanceof VoidType or
body.getLastStmt() = nonReturningStmt()
)
}
/**
* Gets a virtual method that always throws an exception or calls `exit`.
*/
private EffectivelyNonVirtualMethod likelyNonReturningMethod() {
result.getReturnType() instanceof VoidType and
not exists(ReturnStmt ret | ret.getEnclosingCallable() = result) and
not expectedReturn(result) and
forall(Parameter p | p = result.getAParameter() | exists(p.getAnAccess())) and
result.getBody().getLastStmt() = nonReturningStmt()
}
/**
* Gets a `MethodCall` that always throws an exception or calls `exit`.
*/
private MethodCall nonReturningMethodCall() {
methodCallUnconditionallyThrows(result) or
result.getMethod().getSourceDeclaration() = nonReturningMethod() or
result = likelyNonReturningMethod().getAnAccess()
}
/**
* Gets a statement that always throws an exception or calls `exit`.
*/
private Stmt nonReturningStmt() {
result instanceof ThrowStmt
or
result.(ExprStmt).getExpr() = nonReturningExpr()
or
result.(BlockStmt).getLastStmt() = nonReturningStmt()
or
exists(IfStmt ifstmt | ifstmt = result |
ifstmt.getThen() = nonReturningStmt() and
ifstmt.getElse() = nonReturningStmt()
)
or
exists(TryStmt try | try = result |
try.getBlock() = nonReturningStmt() and
forall(CatchClause cc | cc = try.getACatchClause() | cc.getBlock() = nonReturningStmt())
)
}
/**
* Gets an expression that always throws an exception or calls `exit`.
*/
private Expr nonReturningExpr() {
result = nonReturningMethodCall()
or
result.(StmtExpr).getStmt() = nonReturningStmt()
or
exists(WhenExpr whenexpr | whenexpr = result |
whenexpr.getBranch(_).isElseBranch() and
forex(WhenBranch whenbranch | whenbranch = whenexpr.getBranch(_) |
whenbranch.getRhs() = nonReturningStmt()
)
)
}
// Join order engineering -- first determine the switch block and the case indices required, then retrieve them.
bindingset[switch, i]
pragma[inline_late]
private predicate isNthCaseOf(SwitchBlock switch, SwitchCase c, int i) {
c.isNthCaseOf(switch, i)
}
/**
* Gets a `SwitchCase` that may be `pred`'s direct successor, where `pred` is declared in block `switch`.
*
* This means any switch case that comes after `pred` up to the next pattern case, if any, except for `case null`.
*
* Because we know the switch block contains at least one pattern, we know by https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11
* that any default case comes after the last pattern case.
*/
private SwitchCase getASuccessorSwitchCase(PatternCase pred, SwitchBlock switch) {
// Note we do include `case null, default` (as well as plain old `default`) here.
not result.(ConstCase).getValue(_) instanceof NullLiteral and
exists(int maxCaseIndex |
switch = pred.getParent() and
if exists(getNextPatternCase(pred))
then maxCaseIndex = getNextPatternCase(pred).getCaseIndex()
else maxCaseIndex = lastCaseIndex(switch)
|
isNthCaseOf(switch, result, [pred.getCaseIndex() + 1 .. maxCaseIndex])
)
}
/**
* Gets a `SwitchCase` that may occur first in `switch`.
*
* If the block contains at least one PatternCase, this is any case up to and including that case, or
* the case handling the null literal if any.
*
* Otherwise it is any case in the switch block.
*/
private SwitchCase getAFirstSwitchCase(SwitchBlock switch) {
result.getParent() = switch and
(
result.(ConstCase).getValue(_) instanceof NullLiteral
or
result instanceof NullDefaultCase
or
not exists(getFirstPatternCase(switch))
or
result.getIndex() <= getFirstPatternCase(switch).getIndex()
)
}
private Stmt getSwitchStatement(SwitchBlock switch, int i) { result.isNthChildOf(switch, i) }
/**
* Holds if `last` is the last node in any of pattern case `pc`'s succeeding bind-and-test operations,
* immediately before either falling through to execute successor statements or execute a rule body
* if present. `completion` is the completion kind of the last operation.
*/
private predicate lastPatternCaseMatchingOp(PatternCase pc, Node last, Completion completion) {
last(pc.getAPattern(), last, completion) and
completion = NormalCompletion() and
not exists(pc.getGuard())
or
last(pc.getGuard(), last, completion) and
completion = BooleanCompletion(true, _)
}
/**
* Expressions and statements with CFG edges in post-order AST traversal.
*
* This includes most expressions, except those that initiate or propagate branching control
* flow (`LogicExpr`, `ConditionalExpr`).
* Only a few statements are included; those with specific side-effects
* occurring after the evaluation of their children, that is, `Call`, `ReturnStmt`,
* and `ThrowStmt`. CFG nodes without child nodes in the CFG that may complete
* normally are also included.
*/
private class PostOrderNode extends AstNode {
PostOrderNode() {
// For VarAccess and ArrayAccess only read accesses (r-values) are included,
// as write accesses aren't included in the CFG.
this instanceof ArrayAccess and not exists(AssignExpr a | this = a.getDest())
or
this instanceof ArrayCreationExpr
or
this instanceof ArrayInit
or
this instanceof Assignment
or
this instanceof BinaryExpr and not this instanceof LogicExpr
or
this instanceof UnaryExpr and not this instanceof LogNotExpr
or
this instanceof CastingExpr
or
this instanceof InstanceOfExpr and not this.(InstanceOfExpr).isPattern()
or
this instanceof NotInstanceOfExpr
or
this instanceof LocalVariableDeclExpr
or
this instanceof StringTemplateExpr
or
this instanceof ClassExpr
or
this instanceof VarRead
or
this instanceof Call // includes both expressions and statements
or
this instanceof ErrorExpr
or
this instanceof ReturnStmt
or
this instanceof ThrowStmt
or
this instanceof Literal
or
this instanceof TypeLiteral
or
this instanceof ThisAccess
or
this instanceof SuperAccess
or
this.(BlockStmt).getNumStmt() = 0
or
this instanceof SwitchCase and
not this.(SwitchCase).isRule() and
not this instanceof PatternCase
or
this instanceof RecordPatternExpr
or
this instanceof EmptyStmt
or
this instanceof LocalTypeDeclStmt
}
/** Gets child nodes in their order of execution. Indexing starts at either -1 or 0. */
AstNode getChildNode(int index) {
exists(ArrayAccess e | e = this |
index = 0 and result = e.getArray()
or
index = 1 and result = e.getIndexExpr()
)
or
exists(ArrayCreationExpr e | e = this |
result = e.getDimension(index)
or
index = count(e.getADimension()) and result = e.getInit()
)
or
result = this.(ArrayInit).getInit(index) and index >= 0
or
exists(AssignExpr e, ArrayAccess lhs | e = this and lhs = e.getDest() |
index = 0 and result = lhs.getArray()
or
index = 1 and result = lhs.getIndexExpr()
or
index = 2 and result = e.getSource()
)
or
exists(AssignExpr e, VarAccess lhs | e = this and lhs = e.getDest() |
index = -1 and result = lhs.getQualifier() and not result instanceof TypeAccess
or
index = 0 and result = e.getSource()
)
or
exists(AssignOp e | e = this |
index = 0 and result = e.getDest()
or
index = 1 and result = e.getRhs()
)
or
exists(BinaryExpr e | e = this |
index = 0 and result = e.getLeftOperand()
or
index = 1 and result = e.getRightOperand()
)
or
index = 0 and result = this.(UnaryExpr).getExpr()
or
index = 0 and result = this.(CastingExpr).getExpr()
or
index = 0 and result = this.(InstanceOfExpr).getExpr()
or
index = 0 and result = this.(NotInstanceOfExpr).getExpr()
or
index = 0 and result = this.(LocalVariableDeclExpr).getInit()
or
index = 0 and result = this.(VarRead).getQualifier() and not result instanceof TypeAccess
or
exists(Call e | e = this |
index = -1 and result = e.getQualifier() and not result instanceof TypeAccess
or
result = e.getArgument(index)
)
or
exists(StringTemplateExpr e | e = this | result = e.getComponent(index))
or
index = 0 and result = this.(ClassExpr).getExpr()
or
index = 0 and result = this.(ReturnStmt).getResult()
or
index = 0 and result = this.(ThrowStmt).getExpr()
or
result = this.(RecordPatternExpr).getSubPattern(index)
}
/** Gets the first child node, if any. */
AstNode firstChild() {
result = this.getChildNode(-1)
or
result = this.getChildNode(0) and not exists(this.getChildNode(-1))
}
/** Holds if this CFG node has any child nodes. */
predicate isLeafNode() { not exists(this.getChildNode(_)) }
/** Holds if this node can finish with a `normalCompletion`. */
predicate mayCompleteNormally() {
not this instanceof BooleanLiteral and
not this instanceof ReturnStmt and
not this instanceof ThrowStmt and
not this = nonReturningMethodCall()
}
}
/**
* If the body of `loop` finishes with `completion`, the loop will
* continue executing (provided the loop condition still holds).
*/
private predicate continues(Completion completion, LoopStmt loop) {
completion = NormalCompletion()
or
// only consider continue completions if there actually is a `continue`
// somewhere inside this loop; we don't particularly care whether that
// `continue` could actually target this loop, we just want to restrict
// the size of the predicate
exists(ContinueStmt cnt | cnt.getEnclosingStmt+() = loop |
completion = anonymousContinueCompletion() or
completion = labelledContinueCompletion(getLabel(loop))
)
}
/**
* Determine the part of the AST node `n` that will be executed first.
*/
private Node first(AstNode n) {
result.asExpr() = n and n instanceof LogicExpr
or
result.asExpr() = n and n instanceof ConditionalExpr
or
result.asExpr() = n and n instanceof WhenExpr
or
result.asStmt() = n and n instanceof WhenBranch
or
result.asExpr() = n and n instanceof StmtExpr
or
result = n.getCfgNode() and n.(PostOrderNode).isLeafNode()
or
result = first(n.(PostOrderNode).firstChild())
or
result = first(n.(InstanceOfExpr).getExpr())
or
result = first(n.(SynchronizedStmt).getExpr())
or
result = first(n.(AssertStmt).getExpr())
or
result.asStmt() = n and
not n instanceof PostOrderNode and
not n instanceof SynchronizedStmt and
not n instanceof AssertStmt
or
result.asExpr() = n and n instanceof SwitchExpr
}
/**
* Bind `last` to a node inside the body of `try` that may finish with `completion`
* such that control will be transferred to a `catch` block or the `finally` block of `try`.
*
* In other words, `last` is either a resource declaration that throws, or a
* node in the `try` block that may not complete normally, or a node in
* the `try` block that has no control flow successors inside the block.
*/
private predicate catchOrFinallyCompletion(TryStmt try, Node last, Completion completion) {
last(try.getBlock(), last, completion)
or
last(try.getAResource(), last, completion) and completion = ThrowCompletion(_)
}
/**
* Bind `last` to a node inside the body of `try` that may finish with `completion`
* such that control may be transferred to the `finally` block (if it exists).
*
* In other words, if `last` throws an exception it is possibly not caught by any
* of the catch clauses.
*/
private predicate uncaught(TryStmt try, Node last, Completion completion) {
catchOrFinallyCompletion(try, last, completion) and
(
exists(ThrowableType thrown |
thrown = thrownInBody(try) and
completion = ThrowCompletion(thrown) and
not mustCatch(try.getACatchClause(), thrown)
)
or
completion = NormalCompletion()
or
completion = ReturnCompletion()
or
completion = anonymousBreakCompletion()
or
completion = labelledBreakCompletion(_)
or
completion = anonymousContinueCompletion()
or
completion = labelledContinueCompletion(_)
)
}
/**
* Bind `last` to a node inside `try` that may finish with `completion` such
* that control may be transferred to the `finally` block (if it exists).
*
* This is similar to `uncaught`, but also includes final statements of `catch`
* clauses.
*/
private predicate finallyPred(TryStmt try, Node last, Completion completion) {
uncaught(try, last, completion) or
last(try.getACatchClause(), last, completion)
}
private predicate lastInFinally(TryStmt try, Node last) {
last(try.getFinally(), last, NormalCompletion())
}
private predicate isNextNormalSwitchStmt(SwitchBlock switch, Stmt pred, Stmt succ) {
exists(int i, Stmt immediateSucc |
getSwitchStatement(switch, i) = pred and
getSwitchStatement(switch, i + 1) = immediateSucc and
(
if immediateSucc instanceof PatternCase
then isNextNormalSwitchStmt(switch, immediateSucc, succ)
else succ = immediateSucc
)
)
}
/**
* Bind `last` to a cfg node nested inside `n` (or, indeed, `n` itself) such
* that `last` may be the last node during an execution of `n` and finish
* with the given completion.
*
* A `booleanCompletion` implies that `n` is an `Expr`. Any abnormal
* completion besides `throwCompletion` implies that `n` is a `Stmt`.
*/
private predicate last(AstNode n, Node last, Completion completion) {
// Exceptions are propagated from any sub-expression.
// As are any break, yield, continue, or return completions.
exists(Expr e | e.getParent() = n |
last(e, last, completion) and not completion instanceof NormalOrBooleanCompletion
)
or
// If an expression doesn't finish with a throw completion, then it executes normally with
// either a `normalCompletion` or a `booleanCompletion`.
// A boolean completion in a non-boolean context just indicates a normal completion
// and a normal completion in a boolean context indicates an arbitrary boolean completion.
last(n, last, NormalCompletion()) and
inBooleanContext(n) and
completion = basicBooleanCompletion(_)
or
last(n, last, BooleanCompletion(_, _)) and
not inBooleanContext(n) and
completion = NormalCompletion() and
// PatternCase has both a boolean-true completion (guard success) and a normal one
// (variable declaration completion, when no guard is present).
not n instanceof PatternCase
or
// Logic expressions and conditional expressions are executed in AST pre-order to facilitate
// proper short-circuit representation. All other expressions are executed in post-order.
// The last node of a logic expression is either in the right operand with an arbitrary
// completion, or in the left operand with the corresponding boolean completion.
exists(AndLogicalExpr andexpr | andexpr = n |
last(andexpr.getLeftOperand(), last, completion) and completion = BooleanCompletion(false, _)
or
last(andexpr.getRightOperand(), last, completion)
)
or
exists(OrLogicalExpr orexpr | orexpr = n |
last(orexpr.getLeftOperand(), last, completion) and completion = BooleanCompletion(true, _)
or
last(orexpr.getRightOperand(), last, completion)
)
or
// The last node of a `LogNotExpr` is in its sub-expression with an inverted boolean completion
// (or a `normalCompletion`).
exists(Completion subcompletion | last(n.(LogNotExpr).getExpr(), last, subcompletion) |
subcompletion = NormalCompletion() and
completion = NormalCompletion() and
not inBooleanContext(n)
or
exists(boolean outervalue, boolean innervalue |
subcompletion = BooleanCompletion(outervalue, innervalue) and
completion = BooleanCompletion(outervalue.booleanNot(), innervalue)
)
)
or
// The last node of a `ConditionalExpr` is in either of its branches.
exists(ConditionalExpr condexpr | condexpr = n |
last(condexpr.getABranchExpr(), last, completion)
)
or
exists(InstanceOfExpr ioe | ioe.isPattern() and ioe = n |
last.asExpr() = n and completion = basicBooleanCompletion(false)
or
last(ioe.getPattern(), last, NormalCompletion()) and completion = basicBooleanCompletion(true)
)
or
// The last node of a node executed in post-order is the node itself.
exists(PostOrderNode p | p = n |
p.mayCompleteNormally() and last = p.getCfgNode() and completion = NormalCompletion()
)
or
last.asExpr() = n and completion = basicBooleanCompletion(n.(BooleanLiteral).getBooleanValue())
or
// The last statement in a block is any statement that does not complete normally,
// or the last statement.
exists(BlockStmt blk | blk = n |
last(blk.getAStmt(), last, completion) and completion != NormalCompletion()
or
last(blk.getStmt(blk.getNumStmt() - 1), last, completion)
)
or
// The last node in an `if` statement is the last node in either of its branches or
// the last node of the condition with a false-completion in the absence of an else-branch.
exists(IfStmt ifstmt | ifstmt = n |
last(ifstmt.getCondition(), last, BooleanCompletion(false, _)) and
completion = NormalCompletion() and
not exists(ifstmt.getElse())
or
last(ifstmt.getThen(), last, completion)
or
last(ifstmt.getElse(), last, completion)
)
or
// A loop may terminate normally if its condition is false...
exists(LoopStmt loop | loop = n |
last(loop.getCondition(), last, BooleanCompletion(false, _)) and
completion = NormalCompletion()
or
// ...or if it's an enhanced for loop running out of items to iterate over...
// ...which may happen either immediately after the loop expression...
last(loop.(EnhancedForStmt).getExpr(), last, completion) and completion = NormalCompletion()
or
exists(Completion bodyCompletion | last(loop.getBody(), last, bodyCompletion) |
// ...or after the last node in the loop's body in an iteration that would otherwise continue.
loop instanceof EnhancedForStmt and
continues(bodyCompletion, loop) and
completion = NormalCompletion()
or
// Otherwise the last node is the last node in the loop's body...
// ...if it is an unlabelled `break` (causing the entire loop to complete normally)
(
if bodyCompletion = anonymousBreakCompletion()
then completion = NormalCompletion()
else (
// ...or if it is some other completion that does not continue the loop.
not continues(bodyCompletion, loop) and completion = bodyCompletion
)
)
)
)
or
// `try` statements are a bit more complicated:
exists(TryStmt try | try = n |
// the last node in a `try` is the last node in its `finally` block
// if the `finally` block completes normally, it resumes any completion that
// was current before the `finally` block was entered
lastInFinally(try, last) and
finallyPred(try, _, completion)
or
// otherwise, just take the completion of the `finally` block itself
last(try.getFinally(), last, completion) and
completion != NormalCompletion()
or
// if there is no `finally` block, take the last node of the body or
// any of the `catch` clauses
not exists(try.getFinally()) and finallyPred(try, last, completion)
)
or
// handle `switch` statements
exists(SwitchStmt switch | switch = n |
// unlabelled `break` causes the whole `switch` to complete normally
last(switch.getAStmt(), last, anonymousBreakCompletion()) and
completion = NormalCompletion()
or
// any other abnormal completion is propagated
last(switch.getAStmt(), last, completion) and
completion != anonymousBreakCompletion() and
not completion instanceof NormalOrBooleanCompletion
or
// if a statement without a non-pattern-case successor completes normally (or for a pattern case
// the guard succeeds) then the switch completes normally.
exists(Stmt lastNormalStmt, Completion stmtCompletion |
lastNormalStmt = getSwitchStatement(switch, _) and
not isNextNormalSwitchStmt(switch, lastNormalStmt, _) and
last(lastNormalStmt, last, stmtCompletion) and
(stmtCompletion = NormalCompletion() or stmtCompletion = BooleanCompletion(true, _)) and
completion = NormalCompletion()
)
or
// if no default case exists, then normal completion of the expression may terminate the switch
// Note this can't happen if there are pattern cases or a null literal, as
// https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11.2 requires that such
// an enhanced switch block is exhaustive.
not exists(switch.getDefaultCase()) and
not exists(switch.getAPatternCase()) and
not switch.hasNullCase() and
last(switch.getExpr(), last, completion) and
completion = NormalCompletion()
)
or
// handle `switch` expression
exists(SwitchExpr switch | switch = n |
// `yield` terminates the `switch`
last(switch.getAStmt(), last, YieldCompletion(completion))
or
// any other abnormal completion is propagated
last(switch.getAStmt(), last, completion) and
not completion instanceof YieldCompletion and
not completion instanceof NormalOrBooleanCompletion
)
or
// If a case rule right-hand-side completes then the switch breaks or yields, depending
// on whether this is a switch expression or statement. If it completes abruptly then the
// switch completes the same way.
exists(Completion caseCompletion, SwitchCase case |
case = n and
(
last(case.getRuleStatement(), last, caseCompletion)
or
last(case.getRuleExpression(), last, caseCompletion)
)
|
if caseCompletion instanceof NormalOrBooleanCompletion
then
case.getParent() instanceof SwitchStmt and completion = anonymousBreakCompletion()
or
case.getParent() instanceof SwitchExpr and completion = YieldCompletion(caseCompletion)
else completion = caseCompletion
)
or
// A pattern case statement can complete:
// * On failure of its final type test (boolean false)
// * On failure of its guard test if any (boolean false)
// * On completion of one of its pattern variable declarations, if it is not a rule and has no guard (normal completion)
// * On success of its guard test, if it is not a rule (boolean true)
// (the latter two cases are accounted for by lastPatternCaseMatchingOp)
exists(PatternCase pc | n = pc |
last.asStmt() = pc and completion = basicBooleanCompletion(false)
or
last(pc.getGuard(), last, completion) and
completion = BooleanCompletion(false, _)
or
not pc.isRule() and
lastPatternCaseMatchingOp(pc, last, completion)
)
or
// the last statement of a synchronized statement is the last statement of its body
last(n.(SynchronizedStmt).getBlock(), last, completion)
or
// `return` statements give rise to a `Return` completion
last.asStmt() = n.(ReturnStmt) and completion = ReturnCompletion()
or
exists(AssertStmt assertstmt | assertstmt = n |
// `assert` statements may complete normally - we use the `AssertStmt` itself
// to represent this outcome
last.asStmt() = assertstmt and completion = NormalCompletion()
or
// `assert` statements may throw
completion = ThrowCompletion(assertionError()) and
last.(AssertThrowNode).getAstNode() = assertstmt
)
or
// `throw` statements or throwing calls give rise to `Throw` completion
exists(ThrowableType tt | mayThrow(n, tt) |
last = n.getCfgNode() and completion = ThrowCompletion(tt)
)
or
// `break` statements give rise to a `Break` completion
exists(BreakStmt break | break = n and last.asStmt() = n |
completion = labelledBreakCompletion(MkLabel(break.getLabel()))
or
not exists(break.getLabel()) and completion = anonymousBreakCompletion()
)
or
// yield statements get their completion wrapped as a yield
exists(Completion caseCompletion |
last(n.(YieldStmt).getValue(), last, caseCompletion) and
if caseCompletion instanceof NormalOrBooleanCompletion
then completion = YieldCompletion(caseCompletion)
else completion = caseCompletion
)
or
// `continue` statements give rise to a `Continue` completion
exists(ContinueStmt cont | cont = n and last.asStmt() = n |
completion = labelledContinueCompletion(MkLabel(cont.getLabel()))
or
not exists(cont.getLabel()) and completion = anonymousContinueCompletion()
)
or
// the last node in an `ExprStmt` is the last node in the expression
last(n.(ExprStmt).getExpr(), last, completion) and
completion instanceof NormalOrBooleanCompletion
or
// the last node in a `StmtExpr` is the last node in the statement
last(n.(StmtExpr).getStmt(), last, completion)
or
// the last statement of a labeled statement is the last statement of its body...
exists(LabeledStmt lbl, Completion bodyCompletion |
lbl = n and last(lbl.getStmt(), last, bodyCompletion)
|
// ...except if it's a `break` that refers to this labelled statement
if bodyCompletion = labelledBreakCompletion(MkLabel(lbl.getLabel()))
then completion = NormalCompletion()
else completion = bodyCompletion
)
or
// the last statement of a `catch` clause is the last statement of its block
last(n.(CatchClause).getBlock(), last, completion)
or
// the last node in a variable declaration statement is in the last of its individual declarations
exists(LocalVariableDeclStmt s | s = n |
last(s.getVariable(count(s.getAVariable())), last, completion) and
completion = NormalCompletion()
)
or
// The last node in a `when` expression is the last node in any of its branches or
// the last node of the condition of the last branch in the absence of an else-branch.
exists(WhenExpr whenexpr | whenexpr = n |
// If we have no branches then we are the last node
last.asExpr() = n and
completion = NormalCompletion() and
not exists(whenexpr.getBranch(_))
or
// If our last branch condition is false then we are done
exists(int i |
last(whenexpr.getBranch(i), last, BooleanCompletion(false, _)) and
completion = NormalCompletion() and
not exists(whenexpr.getBranch(i + 1))
)
or
// Any branch getting an abnormal completion is propagated
last(whenexpr.getBranch(_), last, completion) and
not completion instanceof YieldCompletion and
not completion instanceof NormalOrBooleanCompletion
or
// The last node in any branch. This will be wrapped up as a
// YieldCompletion, so we need to unwrap it here.
last(whenexpr.getBranch(_), last, YieldCompletion(completion))
)
or
exists(WhenBranch whenbranch | whenbranch = n |
// If the condition completes with anything other than true
// (or "normal", which we will also see if we don't know how
// to make specific true/false edges for the condition)
// (e.g. false or an exception), then the branch is done.
last(whenbranch.getCondition(), last, completion) and
not completion = BooleanCompletion(true, _) and
not completion = NormalCompletion()
or
// Similarly any non-normal completion of the RHS
// should propagate outwards:
last(whenbranch.getRhs(), last, completion) and
not completion instanceof NormalOrBooleanCompletion
or
// Otherwise we wrap the completion up in a YieldCompletion
// so that the `when` expression can tell that we have finished,
// and it shouldn't go on to the next branch.
exists(Completion branchCompletion |
last(whenbranch.getRhs(), last, branchCompletion) and
completion = YieldCompletion(branchCompletion)
)
)
}
/**
* Compute the intra-procedural successors of cfg node `n`, assuming its
* execution finishes with the given completion.
*/
cached
Node succ(Node n, Completion completion) {
// After executing the callable body, the final nodes are first the
// annotated exit node and then the final exit node.
exists(Callable c | last(c.getBody(), n, completion) |
if completion instanceof ThrowCompletion
then result.(ExceptionalExitNode).getEnclosingCallable() = c
else result.(NormalExitNode).getEnclosingCallable() = c
)
or
completion = NormalCompletion() and
n.(AnnotatedExitNode).getEnclosingCallable() = result.(ExitNode).getEnclosingCallable()
or
// Logic expressions and conditional expressions execute in AST pre-order.
completion = NormalCompletion() and
(
result = first(n.asExpr().(AndLogicalExpr).getLeftOperand()) or
result = first(n.asExpr().(OrLogicalExpr).getLeftOperand()) or
result = first(n.asExpr().(LogNotExpr).getExpr()) or
result = first(n.asExpr().(ConditionalExpr).getCondition())
)
or
// If a logic expression doesn't short-circuit then control flows from its left operand to its right.
exists(AndLogicalExpr e |
last(e.getLeftOperand(), n, completion) and
completion = BooleanCompletion(true, _) and
result = first(e.getRightOperand())
)
or
exists(OrLogicalExpr e |
last(e.getLeftOperand(), n, completion) and
completion = BooleanCompletion(false, _) and
result = first(e.getRightOperand())
)
or
// Control flows to the corresponding branch depending on the boolean completion of the condition.
exists(ConditionalExpr e, boolean branch |
last(e.getCondition(), n, completion) and
completion = BooleanCompletion(branch, _) and
result = first(e.getBranchExpr(branch))
)
or
exists(InstanceOfExpr ioe | ioe.isPattern() |
last(ioe.getExpr(), n, completion) and
completion = NormalCompletion() and
result.asExpr() = ioe
or
n.asExpr() = ioe and
result = first(ioe.getPattern()) and
completion = basicBooleanCompletion(true)
)
or
// In other expressions control flows from left to right and ends in the node itself.
exists(PostOrderNode p, int i |
last(p.getChildNode(i), n, completion) and completion = NormalCompletion()
|
result = first(p.getChildNode(i + 1))
or
not exists(p.getChildNode(i + 1)) and result = p.getCfgNode()
)
or
// Statements within a block execute sequentially.
result = first(n.asStmt().(BlockStmt).getStmt(0)) and completion = NormalCompletion()
or
exists(BlockStmt blk, int i |
last(blk.getStmt(i), n, completion) and
completion = NormalCompletion() and
result = first(blk.getStmt(i + 1))
)
or
// Control flows to the corresponding branch depending on the boolean completion of the condition.
exists(IfStmt s |
n.asStmt() = s and result = first(s.getCondition()) and completion = NormalCompletion()
or
last(s.getCondition(), n, completion) and
completion = BooleanCompletion(true, _) and
result = first(s.getThen())
or
last(s.getCondition(), n, completion) and
completion = BooleanCompletion(false, _) and
result = first(s.getElse())
)
or
// For statements:
exists(ForStmt for, Node condentry |
// Any part of the control flow that aims for the condition needs to hit either the condition...
condentry = first(for.getCondition())
or
// ...or the body if the for doesn't include a condition.
not exists(for.getCondition()) and condentry = first(for.getStmt())
|
// From the entry point, which is the for statement itself, control goes to either the first init expression...
n.asStmt() = for and result = first(for.getInit(0)) and completion = NormalCompletion()
or
// ...or the condition if the for doesn't include init expressions.
n.asStmt() = for and
not exists(for.getAnInit()) and
result = condentry and
completion = NormalCompletion()
or
// Init expressions execute sequentially, after which control is transferred to the condition.
exists(int i | last(for.getInit(i), n, completion) and completion = NormalCompletion() |
result = first(for.getInit(i + 1))
or
not exists(for.getInit(i + 1)) and result = condentry
)
or
// The true-successor of the condition is the body of the for loop.
last(for.getCondition(), n, completion) and
completion = BooleanCompletion(true, _) and
result = first(for.getStmt())
or
// The updates execute sequentially, after which control is transferred to the condition.
exists(int i | last(for.getUpdate(i), n, completion) and completion = NormalCompletion() |
result = first(for.getUpdate(i + 1))
or
not exists(for.getUpdate(i + 1)) and result = condentry
)
or
// The back edge of the loop: control goes to either the first update or the condition if no updates exist.
last(for.getStmt(), n, completion) and
continues(completion, for) and
(
result = first(for.getUpdate(0))
or
result = condentry and not exists(for.getAnUpdate())
)
)
or
// Enhanced for statements:
exists(EnhancedForStmt for |
// First the expression gets evaluated...
n.asStmt() = for and result = first(for.getExpr()) and completion = NormalCompletion()
or
// ...then the variable gets assigned...
last(for.getExpr(), n, completion) and
completion = NormalCompletion() and
result.asExpr() = for.getVariable()
or
// ...and then control goes to the body of the loop.
n.asExpr() = for.getVariable() and
result = first(for.getStmt()) and
completion = NormalCompletion()
or
// Finally, the back edge of the loop goes to reassign the variable.
last(for.getStmt(), n, completion) and
continues(completion, for) and
result.asExpr() = for.getVariable()
)
or
// While loops start at the condition...
result = first(n.asStmt().(WhileStmt).getCondition()) and completion = NormalCompletion()
or
// ...and do-while loops start at the body.
result = first(n.asStmt().(DoStmt).getStmt()) and completion = NormalCompletion()
or
exists(LoopStmt loop | loop instanceof WhileStmt or loop instanceof DoStmt |
// Control goes from the condition via a true-completion to the body...
last(loop.getCondition(), n, completion) and
completion = BooleanCompletion(true, _) and
result = first(loop.getBody())
or
// ...and through the back edge from the body back to the condition.
last(loop.getBody(), n, completion) and
continues(completion, loop) and
result = first(loop.getCondition())
)
or
// Resource declarations in a try-with-resources execute sequentially.
exists(TryStmt try, int i |
last(try.getResource(i), n, completion) and completion = NormalCompletion()
|
result = first(try.getResource(i + 1))
or
not exists(try.getResource(i + 1)) and result = first(try.getBlock())
)
or
// After the last resource declaration, control transfers to the body.
exists(TryStmt try | n.asStmt() = try and completion = NormalCompletion() |
result = first(try.getResource(0))
or
not exists(try.getAResource()) and result = first(try.getBlock())
)
or
// exceptional control flow
exists(TryStmt try | catchOrFinallyCompletion(try, n, completion) |
// if the body of the `try` throws...
exists(ThrowableType tt | completion = ThrowCompletion(tt) |
// ...control transfers to a catch clause...
result = first(handlingCatchClause(try, tt))
or
// ...or to the finally block
not mustCatch(try.getACatchClause(), tt) and result = first(try.getFinally())
)
or
// if the body completes normally, control transfers to the finally block
not completion = ThrowCompletion(_) and result = first(try.getFinally())
)
or
// after each catch clause, control transfers to the finally block
exists(TryStmt try | last(try.getACatchClause(), n, completion) |
result = first(try.getFinally())
)
or
// Catch clauses first assign their variable and then execute their block
exists(CatchClause cc | completion = NormalCompletion() |
n.asStmt() = cc and result = first(cc.getVariable())
or
last(cc.getVariable(), n, completion) and result = first(cc.getBlock())
)
or
// Switch statements and expressions
exists(SwitchBlock switch |
exists(Expr switchExpr |
switchExpr = switch.(SwitchStmt).getExpr() or switchExpr = switch.(SwitchExpr).getExpr()
|
// From the entry point control is transferred first to the expression...
n.getAstNode() = switch and
result = first(switchExpr) and
completion = NormalCompletion()
or
// ...and then to any case up to and including the first pattern case, if any.
last(switchExpr, n, completion) and
result = first(getAFirstSwitchCase(switch)) and
completion = NormalCompletion()
)
or
// Statements within a switch body execute sequentially.
// Note this includes non-rule case statements and the successful pattern match successor
// of a non-rule pattern case statement. Rule case statements do not complete normally
// (they always break or yield).
// Exception: falling through into a pattern case statement (which necessarily does not
// declare any named variables) must skip one or more such statements, otherwise we would
// incorrectly apply their type test and/or guard.
exists(Stmt pred, Stmt succ |
isNextNormalSwitchStmt(switch, pred, succ) and
last(pred, n, completion) and
result = first(succ) and
(completion = NormalCompletion() or completion = BooleanCompletion(true, _))
)
or
// A pattern case that completes boolean false (type test or guard failure) continues to consider other cases:
exists(PatternCase case | completion = BooleanCompletion(false, _) |
last(case, n, completion) and result.asStmt() = getASuccessorSwitchCase(case, switch)
)
)
or
// Pattern cases have internal edges:
// * Type test success -true-> one of the possible sets of variable declarations
// n.b. for unnamed patterns (e.g. case A _, B _) this means that *one* of the
// type tests has succeeded. There aren't enough nodes in the AST to describe
// a sequential test in detail, so CFG consumers have to watch out for this case.
// * Variable declarations -normal-> guard evaluation
// * Variable declarations -normal-> rule execution (when there is no guard)
// * Guard success -true-> rule execution
exists(PatternCase pc |
n.asStmt() = pc and
completion = basicBooleanCompletion(true) and
result = first(pc.getAPattern())
or
last(pc.getAPattern(), n, completion) and
completion = NormalCompletion() and
result = first(pc.getGuard())
or
lastPatternCaseMatchingOp(pc, n, completion) and
(
result = first(pc.getRuleExpression())
or
result = first(pc.getRuleStatement())
)
)
or
// Non-pattern cases have an internal edge leading to their rule body if any when the case matches.
exists(SwitchCase case | n.asStmt() = case |
not case instanceof PatternCase and
completion = NormalCompletion() and
(
result = first(case.getRuleExpression())
or
result = first(case.getRuleStatement())
)
)
or
// Yield
exists(YieldStmt yield | completion = NormalCompletion() |
n.asStmt() = yield and result = first(yield.getValue())
)
or
// Synchronized statements execute their expression _before_ synchronization, so the CFG reflects that.
exists(SynchronizedStmt synch | completion = NormalCompletion() |
last(synch.getExpr(), n, completion) and result.asStmt() = synch
or
n.asStmt() = synch and result = first(synch.getBlock())
)
or
result = first(n.asStmt().(ExprStmt).getExpr()) and completion = NormalCompletion()
or
result = first(n.asExpr().(StmtExpr).getStmt()) and completion = NormalCompletion()
or
result = first(n.asStmt().(LabeledStmt).getStmt()) and completion = NormalCompletion()
or
// Variable declarations in a variable declaration statement are executed sequentially.
exists(LocalVariableDeclStmt s | completion = NormalCompletion() |
n.asStmt() = s and result = first(s.getVariable(1))
or
exists(int i | last(s.getVariable(i), n, completion) and result = first(s.getVariable(i + 1)))
)
or
// Assert statements:
exists(AssertStmt assertstmt |
last(assertstmt.getExpr(), n, completion) and
completion = BooleanCompletion(true, _) and
result.asStmt() = assertstmt
or
last(assertstmt.getExpr(), n, completion) and
completion = BooleanCompletion(false, _) and
(
result = first(assertstmt.getMessage())
or
not exists(assertstmt.getMessage()) and
result.(AssertThrowNode).getAstNode() = assertstmt
)
or
last(assertstmt.getMessage(), n, completion) and
completion = NormalCompletion() and
result.(AssertThrowNode).getAstNode() = assertstmt
)
or
// When expressions:
exists(WhenExpr whenexpr |
n.asExpr() = whenexpr and
result = first(whenexpr.getBranch(0)) and
completion = NormalCompletion()
or
exists(int i |
last(whenexpr.getBranch(i), n, completion) and
completion = BooleanCompletion(false, _) and
result = first(whenexpr.getBranch(i + 1))
)
)
or
// When branches:
exists(WhenBranch whenbranch |
n.asStmt() = whenbranch and
completion = NormalCompletion() and
result = first(whenbranch.getCondition())
or
last(whenbranch.getCondition(), n, completion) and
completion = BooleanCompletion(true, _) and
result = first(whenbranch.getRhs())
)
}
/*
* Conditions give rise to nodes with two normal successors, a true successor
* and a false successor. At least one of them is completely contained in the
* AST node of the branching construct and is therefore tagged with the
* corresponding `booleanCompletion` in the `succ` relation (for example, the
* then-branch of an if-statement, or the right operand of a binary logic
* expression). The other successor may be tagged with either the corresponding
* `booleanCompletion` (for example in an if-statement with an else-branch or
* in a `ConditionalExpr`) or a `normalCompletion` (for example in an
* if-statement without an else-part).
*
* If the other successor ends a finally block it may also be tagged with an
* abnormal completion instead of a `normalCompletion`. This completion is
* calculated by the `resumption` predicate. In this case the successor might
* no longer be unique, as there can be multiple completions to be resumed
* after the finally block.
*/
/**
* Gets the _resumption_ for cfg node `n`, that is, the completion according
* to which control flow is determined if `n` completes normally.
*
* In most cases, the resumption is simply the normal completion, except if
* `n` is the last node of a `finally` block, in which case it is the
* completion of any predecessors of the `finally` block as determined by
* predicate `finallyPred`, since their completion is resumed after normal
* completion of the `finally`.
*/
private Completion resumption(Node n) {
exists(TryStmt try | lastInFinally(try, n) and finallyPred(try, _, result))
or
not lastInFinally(_, n) and result = NormalCompletion()
}
/**
* A true- or false-successor that is tagged with the corresponding `booleanCompletion`.
*
* That is, the `booleanCompletion` is the label of the edge in the CFG.
*/
private Node mainBranchSucc(Node n, boolean b) { result = succ(n, BooleanCompletion(_, b)) }
/**
* A true- or false-successor that is not tagged with a `booleanCompletion`.
*
* That is, the label of the edge in the CFG is a `normalCompletion` or
* some other completion if `n` occurs as the last node in a finally block.
*
* In the latter case, when `n` occurs as the last node in a finally block, there might be
* multiple different such successors.
*/
private Node otherBranchSucc(Node n, boolean b) {
exists(Node main | main = mainBranchSucc(n, b.booleanNot()) |
result = succ(n, resumption(n)) and
not result = main and
(b = true or b = false)
)
}
/** Gets a true- or false-successor of `n`. */
cached
Node branchSuccessor(Node n, boolean branch) {
result = mainBranchSucc(n, branch) or
result = otherBranchSucc(n, branch)
}
}
private import ControlFlowGraphImpl
/** A control-flow node that branches based on a condition. */
class ConditionNode extends ControlFlow::Node {
ConditionNode() { exists(branchSuccessor(this, _)) }
/** Gets a true- or false-successor of the `ConditionNode`. */
ControlFlow::Node getABranchSuccessor(boolean branch) { result = branchSuccessor(this, branch) }
/** Gets a true-successor of the `ConditionNode`. */
ControlFlow::Node getATrueSuccessor() { result = this.getABranchSuccessor(true) }
/** Gets a false-successor of the `ConditionNode`. */
ControlFlow::Node getAFalseSuccessor() { result = this.getABranchSuccessor(false) }
/** Gets the condition of this `ConditionNode`. */
ExprParent getCondition() { result = this.asExpr() or result = this.asStmt() }
}