mirror of
https://github.com/github/codeql.git
synced 2025-12-18 01:33:15 +01:00
1737 lines
66 KiB
Plaintext
1737 lines
66 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.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 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)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Bind `t` to an unchecked exception that may occur in a precondition check.
|
|
*/
|
|
private predicate uncheckedExceptionFromMethod(MethodCall ma, ThrowableType t) {
|
|
conditionCheckArgument(ma, _, _) and
|
|
(t instanceof TypeError or t instanceof TypeRuntimeException)
|
|
}
|
|
|
|
/**
|
|
* 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() {
|
|
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, 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() }
|
|
}
|