From c98afc17b6c60b848047b21ade7395cb18c01701 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 5 Feb 2026 09:59:38 +0100 Subject: [PATCH 1/5] Cfg: Add ConditionKind and getDual to ConditionalSuccessor. --- .../codeql/controlflow/SuccessorType.qll | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/shared/controlflow/codeql/controlflow/SuccessorType.qll b/shared/controlflow/codeql/controlflow/SuccessorType.qll index cc673c9e5cab..ef772a929728 100644 --- a/shared/controlflow/codeql/controlflow/SuccessorType.qll +++ b/shared/controlflow/codeql/controlflow/SuccessorType.qll @@ -28,6 +28,34 @@ module; private import codeql.util.Boolean +private newtype TConditionKind = + TBooleanCondition() or + TNullnessCondition() or + TMatchingCondition() or + TEmptinessCondition() + +/** A condition kind. This is used to classify different `ConditionalSuccessor`s. */ +class ConditionKind extends TConditionKind { + /** Gets a textual representation of this condition kind. */ + string toString() { + this instanceof TBooleanCondition and result = "Boolean" + or + this instanceof TNullnessCondition and result = "Nullness" + or + this instanceof TMatchingCondition and result = "Matching" + or + this instanceof TEmptinessCondition and result = "Emptiness" + } + + predicate isBoolean() { this instanceof TBooleanCondition } + + predicate isNullness() { this instanceof TNullnessCondition } + + predicate isMatching() { this instanceof TMatchingCondition } + + predicate isEmptiness() { this instanceof TEmptinessCondition } +} + private newtype TSuccessorType = TDirectSuccessor() or TBooleanSuccessor(Boolean branch) or @@ -83,6 +111,18 @@ private class TConditionalSuccessor = abstract private class ConditionalSuccessorImpl extends NormalSuccessorImpl, TConditionalSuccessor { /** Gets the Boolean value of this successor. */ abstract boolean getValue(); + + /** Gets the condition kind of this conditional successor. */ + abstract ConditionKind getKind(); + + /** + * Gets the dual of this conditional successor. That is, the conditional + * successor of the same kind but with the opposite value. + */ + ConditionalSuccessor getDual() { + this.getValue().booleanNot() = result.getValue() and + this.getKind() = result.getKind() + } } final class ConditionalSuccessor = ConditionalSuccessorImpl; @@ -116,6 +156,8 @@ final class ConditionalSuccessor = ConditionalSuccessorImpl; class BooleanSuccessor extends ConditionalSuccessorImpl, TBooleanSuccessor { override boolean getValue() { this = TBooleanSuccessor(result) } + override ConditionKind getKind() { result = TBooleanCondition() } + override string toString() { result = this.getValue().toString() } } @@ -151,6 +193,8 @@ class NullnessSuccessor extends ConditionalSuccessorImpl, TNullnessSuccessor { override boolean getValue() { this = TNullnessSuccessor(result) } + override ConditionKind getKind() { result = TNullnessCondition() } + override string toString() { if this.isNull() then result = "null" else result = "non-null" } } @@ -192,6 +236,8 @@ class MatchingSuccessor extends ConditionalSuccessorImpl, TMatchingSuccessor { override boolean getValue() { this = TMatchingSuccessor(result) } + override ConditionKind getKind() { result = TMatchingCondition() } + override string toString() { if this.isMatch() then result = "match" else result = "no-match" } } @@ -233,6 +279,8 @@ class EmptinessSuccessor extends ConditionalSuccessorImpl, TEmptinessSuccessor { override boolean getValue() { this = TEmptinessSuccessor(result) } + override ConditionKind getKind() { result = TEmptinessCondition() } + override string toString() { if this.isEmpty() then result = "empty" else result = "non-empty" } } From c90831da9ee9ed1ceaa31b20e6490c4ea076de6f Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 6 Feb 2026 15:23:38 +0100 Subject: [PATCH 2/5] Java: Preparatory tweaks. --- java/ql/lib/semmle/code/java/Statement.qll | 2 +- java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll | 2 +- java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll | 2 +- java/ql/src/experimental/quantum/Examples/ArtifactReuse.qll | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 942f5283039e..173ece0ff3ad 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -61,7 +61,7 @@ class Stmt extends StmtParent, ExprParent, @stmt { } /** A statement parent is any element that can have a statement as its child. */ -class StmtParent extends @stmtparent, Top { } +class StmtParent extends @stmtparent, ExprParent { } /** * An error statement. diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll b/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll index 5e3a8550e3cc..962ff0bc169d 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll @@ -132,7 +132,7 @@ private module BaseSsaImpl { inner != outer and inner.getDeclaringType() = innerclass and result = parentDef(desugaredGetEnclosingType*(innerclass)) and - result.getEnclosingStmt().getEnclosingCallable() = outer and + result.getEnclosingCallable() = outer and capturedvar = TLocalVar(outer, v) and closurevar = TLocalVar(inner, v) ) diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll index 409cf5863630..4610f5761684 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll @@ -115,7 +115,7 @@ private ControlFlowNode captureNode(TrackedVar capturedvar, TrackedVar closureva inner != outer and inner.getDeclaringType() = innerclass and result = parentDef(desugaredGetEnclosingType*(innerclass)) and - result.getEnclosingStmt().getEnclosingCallable() = outer and + result.getEnclosingCallable() = outer and capturedvar = TLocalVar(outer, v) and closurevar = TLocalVar(inner, v) ) diff --git a/java/ql/src/experimental/quantum/Examples/ArtifactReuse.qll b/java/ql/src/experimental/quantum/Examples/ArtifactReuse.qll index 776510b52ad1..1a20a7fa2b51 100644 --- a/java/ql/src/experimental/quantum/Examples/ArtifactReuse.qll +++ b/java/ql/src/experimental/quantum/Examples/ArtifactReuse.qll @@ -42,7 +42,7 @@ private DataFlow::Node getGeneratingWrapperSet(Crypto::NonceArtifactNode a) { } private predicate ancestorOfArtifact( - Crypto::ArtifactNode a, Callable enclosingCallable, ControlFlow::Node midOrTarget + Crypto::ArtifactNode a, Callable enclosingCallable, ControlFlowNode midOrTarget ) { a.asElement().(Expr).getEnclosingCallable() = enclosingCallable and ( @@ -87,7 +87,7 @@ predicate isArtifactReuse(Crypto::ArtifactNode a, Crypto::ArtifactNode b) { ancestorOfArtifact(b, commonParent, _) ) implies - exists(Callable commonParent, ControlFlow::Node aMid, ControlFlow::Node bMid | + exists(Callable commonParent, ControlFlowNode aMid, ControlFlowNode bMid | ancestorOfArtifact(a, commonParent, aMid) and ancestorOfArtifact(b, commonParent, bMid) and a instanceof Crypto::NonceArtifactNode and From b5f70fca8e100901f86da5f6111f7ab120fd48ac Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 6 Feb 2026 16:37:43 +0100 Subject: [PATCH 3/5] Java: Replace ControlFlowNode.asCall with Call.getControlFlowNode. --- java/ql/lib/semmle/code/java/ControlFlowGraph.qll | 6 ------ java/ql/lib/semmle/code/java/Expr.qll | 9 +++++++++ java/ql/lib/semmle/code/java/Statement.qll | 6 ++++++ java/ql/lib/semmle/code/java/controlflow/Paths.qll | 2 +- java/ql/lib/semmle/code/java/dataflow/InstanceAccess.qll | 2 +- .../lib/semmle/code/java/dataflow/internal/SsaImpl.qll | 9 ++++++--- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 64449b6f93d7..de1a239faeac 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -162,12 +162,6 @@ module ControlFlow { /** 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() } diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index c609c35cd71e..068ba100be98 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1245,6 +1245,9 @@ class ClassInstanceExpr extends Expr, ConstructorCall, @classinstancexpr { /** Gets the immediately enclosing statement of this class instance creation expression. */ override Stmt getEnclosingStmt() { result = Expr.super.getEnclosingStmt() } + /** Gets the `ControlFlowNode` corresponding to this call. */ + override ControlFlowNode getControlFlowNode() { result = Expr.super.getControlFlowNode() } + /** Gets a printable representation of this expression. */ override string toString() { result = "new " + this.getConstructor().getName() + "(...)" @@ -2113,6 +2116,9 @@ class MethodCall extends Expr, Call, @methodaccess { /** Gets the immediately enclosing statement that contains this method access. */ override Stmt getEnclosingStmt() { result = Expr.super.getEnclosingStmt() } + /** Gets the `ControlFlowNode` corresponding to this call. */ + override ControlFlowNode getControlFlowNode() { result = Expr.super.getControlFlowNode() } + /** Gets a printable representation of this expression. */ override string toString() { if exists(this.getMethod()) @@ -2305,6 +2311,9 @@ class Call extends ExprParent, @caller { /** Gets the enclosing statement of this call. */ /*abstract*/ Stmt getEnclosingStmt() { none() } + /** Gets the `ControlFlowNode` corresponding to this call. */ + /*abstract*/ ControlFlowNode getControlFlowNode() { none() } + /** Gets the number of arguments provided in this call. */ int getNumArgument() { count(this.getAnArgument()) = result } diff --git a/java/ql/lib/semmle/code/java/Statement.qll b/java/ql/lib/semmle/code/java/Statement.qll index 173ece0ff3ad..1c494072f810 100644 --- a/java/ql/lib/semmle/code/java/Statement.qll +++ b/java/ql/lib/semmle/code/java/Statement.qll @@ -960,6 +960,9 @@ class ThisConstructorInvocationStmt extends Stmt, ConstructorCall, @constructori /** Gets the immediately enclosing statement of this constructor invocation. */ override Stmt getEnclosingStmt() { result = this } + /** Gets the `ControlFlowNode` corresponding to this call. */ + override ControlFlowNode getControlFlowNode() { result = Stmt.super.getControlFlowNode() } + override string pp() { result = "this(...)" } override string toString() { result = "this(...)" } @@ -1001,6 +1004,9 @@ class SuperConstructorInvocationStmt extends Stmt, ConstructorCall, @superconstr /** Gets the immediately enclosing statement of this constructor invocation. */ override Stmt getEnclosingStmt() { result = this } + /** Gets the `ControlFlowNode` corresponding to this call. */ + override ControlFlowNode getControlFlowNode() { result = Stmt.super.getControlFlowNode() } + override string pp() { result = "super(...)" } override string toString() { result = "super(...)" } diff --git a/java/ql/lib/semmle/code/java/controlflow/Paths.qll b/java/ql/lib/semmle/code/java/controlflow/Paths.qll index abc56e32b5ca..23f0786966db 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Paths.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Paths.qll @@ -34,7 +34,7 @@ abstract class ActionConfiguration extends string { private BasicBlock actionBlock(ActionConfiguration conf) { exists(ControlFlowNode node | result = node.getBasicBlock() | conf.isAction(node) or - callAlwaysPerformsAction(node.asCall(), conf) + callAlwaysPerformsAction(any(Call call | call.getControlFlowNode() = node), conf) ) } diff --git a/java/ql/lib/semmle/code/java/dataflow/InstanceAccess.qll b/java/ql/lib/semmle/code/java/dataflow/InstanceAccess.qll index feeb0d100c64..60ed5591a95f 100644 --- a/java/ql/lib/semmle/code/java/dataflow/InstanceAccess.qll +++ b/java/ql/lib/semmle/code/java/dataflow/InstanceAccess.qll @@ -229,7 +229,7 @@ class InstanceAccessExt extends TInstanceAccessExt { /** Gets the control flow node associated with this instance access. */ ControlFlowNode getCfgNode() { exists(ExprParent e | e = this.getAssociatedExprOrStmt() | - result.asCall() = e + result = e.(Call).getControlFlowNode() or e.(InstanceAccess).getControlFlowNode() = result or diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll index 4610f5761684..160cf0303922 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/SsaImpl.qll @@ -153,7 +153,7 @@ private predicate hasEntryDef(TrackedVar v, BasicBlock b) { overlay[global] pragma[nomagic] private predicate uncertainVariableUpdateImpl(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { - exists(Call c | c = n.asCall() | updatesNamedField(c, v, _)) and + exists(Call c | c.getControlFlowNode() = n | updatesNamedField(c, v, _)) and b.getNode(i) = n and hasDominanceInformation(b) or @@ -525,8 +525,11 @@ private module Cached { overlay[global] cached predicate defUpdatesNamedField(SsaImplicitWrite calldef, TrackedField f, Callable setter) { - f = calldef.getSourceVariable() and - updatesNamedField0(calldef.getControlFlowNode().asCall(), f, setter) + exists(Call call | + f = calldef.getSourceVariable() and + call.getControlFlowNode() = calldef.getControlFlowNode() and + updatesNamedField0(call, f, setter) + ) } /** Holds if `init` is a closure variable that captures the value of `capturedvar`. */ From 5be62e085ebd2b7bfa7db4b25bae6000c0e34e2c Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 6 Feb 2026 16:39:50 +0100 Subject: [PATCH 4/5] Cfg: Add getEnclosingCallable to shared BasicBlock --- shared/controlflow/codeql/controlflow/BasicBlock.qll | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shared/controlflow/codeql/controlflow/BasicBlock.qll b/shared/controlflow/codeql/controlflow/BasicBlock.qll index ed86ae5aa8be..163ddee70f0b 100644 --- a/shared/controlflow/codeql/controlflow/BasicBlock.qll +++ b/shared/controlflow/codeql/controlflow/BasicBlock.qll @@ -177,6 +177,9 @@ module Make Input> implements CfgSig Date: Tue, 20 Jan 2026 12:43:55 +0100 Subject: [PATCH 5/5] Java/Cfg: Introduce new shared CFG library and replace the Java CFG. --- java/ql/lib/printCfg.ql | 4 +- java/ql/lib/semmle/code/java/Completion.qll | 96 - .../lib/semmle/code/java/ControlFlowGraph.qll | 1831 +++-------------- .../code/java/controlflow/BasicBlocks.qll | 143 -- .../semmle/code/java/controlflow/Paths.qll | 6 +- .../code/java/dataflow/internal/BaseSSA.qll | 16 +- .../code/java/dataflow/internal/SsaImpl.qll | 25 +- .../Termination/ConstantLoopCondition.ql | 18 +- .../Declarations/Common.qll | 44 +- .../controlflow/dominance/dominator.ql | 2 +- .../controlflow/dominance/dominatorUnique.ql | 2 +- .../controlflow/dominance/dominator.ql | 2 +- .../controlflow/dominance/dominatorUnique.ql | 2 +- .../controlflow/dominance/dominator.ql | 2 +- .../controlflow/dominance/dominatorUnique.ql | 2 +- .../java7/MultiCatch/MultiCatchControlFlow.ql | 2 +- .../codeql/controlflow/ControlFlowGraph.qll | 1543 ++++++++++++++ 17 files changed, 1928 insertions(+), 1812 deletions(-) delete mode 100644 java/ql/lib/semmle/code/java/Completion.qll create mode 100644 shared/controlflow/codeql/controlflow/ControlFlowGraph.qll diff --git a/java/ql/lib/printCfg.ql b/java/ql/lib/printCfg.ql index 5e3cc22644ef..d90c4633de85 100644 --- a/java/ql/lib/printCfg.ql +++ b/java/ql/lib/printCfg.ql @@ -21,7 +21,7 @@ external int selectedSourceColumn(); private predicate selectedSourceColumnAlias = selectedSourceColumn/0; -module ViewCfgQueryInput implements ViewCfgQueryInputSig { +module ViewCfgQueryInput implements ControlFlow::ViewCfgQueryInputSig { predicate selectedSourceFile = selectedSourceFileAlias/0; predicate selectedSourceLine = selectedSourceLineAlias/0; @@ -42,4 +42,4 @@ module ViewCfgQueryInput implements ViewCfgQueryInputSig { } } -import ViewCfgQuery +import ControlFlow::ViewCfgQuery diff --git a/java/ql/lib/semmle/code/java/Completion.qll b/java/ql/lib/semmle/code/java/Completion.qll deleted file mode 100644 index 35d3c83e2ee9..000000000000 --- a/java/ql/lib/semmle/code/java/Completion.qll +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Provides classes and predicates for representing completions. - */ -overlay[local?] -module; - -/* - * A completion represents how a statement or expression terminates. - * - * There are five kinds of completions: normal completion, - * `return` completion, `break` completion, - * `continue` completion, and `throw` completion. - * - * Normal completions are further subdivided into boolean completions and all - * other normal completions. A boolean completion adds the information that the - * cfg node terminated with the given boolean value due to a subexpression - * terminating with the other given boolean value. This is only - * relevant for conditional contexts in which the value controls the - * control-flow successor. - */ - -import java - -/** - * A label of a `LabeledStmt`. - */ -newtype Label = MkLabel(string l) { exists(LabeledStmt lbl | l = lbl.getLabel()) } - -/** - * Either a `Label` or nothing. - */ -newtype MaybeLabel = - JustLabel(Label l) or - NoLabel() - -/** - * A completion of a statement or an expression. - */ -newtype Completion = - /** - * The statement or expression completes normally and continues to the next statement. - */ - NormalCompletion() or - /** - * The statement or expression completes by returning from the function. - */ - ReturnCompletion() or - /** - * The expression completes with value `outerValue` overall and with the last control - * flow node having value `innerValue`. - */ - BooleanCompletion(boolean outerValue, boolean innerValue) { - (outerValue = true or outerValue = false) and - (innerValue = true or innerValue = false) - } or - /** - * The expression or statement completes via a `break` statement. - */ - BreakCompletion(MaybeLabel l) or - /** - * The expression or statement completes via a `yield` statement. - */ - YieldCompletion(NormalOrBooleanCompletion c) or - /** - * The expression or statement completes via a `continue` statement. - */ - ContinueCompletion(MaybeLabel l) or - /** - * The expression or statement completes by throwing a `ThrowableType`. - */ - ThrowCompletion(ThrowableType tt) - -/** A completion that is either a `NormalCompletion` or a `BooleanCompletion`. */ -class NormalOrBooleanCompletion extends Completion { - NormalOrBooleanCompletion() { - this instanceof NormalCompletion or this instanceof BooleanCompletion - } - - /** Gets a textual representation of this completion. */ - string toString() { result = "completion" } -} - -/** Gets the completion `ContinueCompletion(NoLabel())`. */ -ContinueCompletion anonymousContinueCompletion() { result = ContinueCompletion(NoLabel()) } - -/** Gets the completion `ContinueCompletion(JustLabel(l))`. */ -ContinueCompletion labelledContinueCompletion(Label l) { result = ContinueCompletion(JustLabel(l)) } - -/** Gets the completion `BreakCompletion(NoLabel())`. */ -BreakCompletion anonymousBreakCompletion() { result = BreakCompletion(NoLabel()) } - -/** Gets the completion `BreakCompletion(JustLabel(l))`. */ -BreakCompletion labelledBreakCompletion(Label l) { result = BreakCompletion(JustLabel(l)) } - -/** Gets the completion `BooleanCompletion(value, value)`. */ -Completion basicBooleanCompletion(boolean value) { result = BooleanCompletion(value, value) } diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index de1a239faeac..a6d34b84edc2 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -1,537 +1,316 @@ /** - * 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. + * Provides classes representing the control flow graph within callables. */ 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.ControlFlowGraph 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) +private module ControlFlow0 = Make0; - /** 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) } +private module ControlFlow1 = Make1; - /** Gets an immediate predecessor of this node. */ - Node getAPredecessor() { this = succ(result) } +private module ControlFlow2 = Make2; - /** Gets an exception successor of this node. */ - Node getAnExceptionSuccessor() { result = succ(this, ThrowCompletion(_)) } +private import ControlFlow0 +private import ControlFlow1 +import ControlFlow2 as ControlFlow - /** 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()) - } +class ControlFlowNode = ControlFlow::ControlFlowNode; - /** 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 - ) - } +module Cfg = ControlFlow::Cfg; - /** Gets the basic block that contains this node. */ - BasicBlock getBasicBlock() { result.getANode() = this } +import Cfg - /** 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) } +module Ast implements AstSig { + private import java as J - /** Gets the expression this `Node` corresponds to, if any. */ - Expr asExpr() { this = TExprNode(result) } + class AstNode = ExprParent; - /** Gets a textual representation of this element. */ - string toString() { none() } + private predicate skipControlFlow(Expr e) { + exists(ConstCase cc | e = cc.getValue(_)) or + e.getParent*() instanceof Annotation or + e instanceof TypeAccess or + e instanceof ArrayTypeAccess or + e instanceof UnionTypeAccess or + e instanceof IntersectionTypeAccess or + e instanceof WildcardTypeAccess + } - /** Gets the source location for this element. */ - Location getLocation() { none() } + AstNode getChild(AstNode n, int index) { + not skipControlFlow(result) and + result.(Expr).isNthChildOf(n, index) + or + result.(Stmt).isNthChildOf(n, index) + } - /** - * Gets the most appropriate AST node for this control flow node, if any. - */ - ExprParent getAstNode() { none() } + Callable getEnclosingCallable(AstNode node) { + result = node.(Expr).getEnclosingCallable() or + result = node.(Stmt).getEnclosingCallable() } - /** A control-flow node that represents the evaluation of an expression. */ - class ExprNode extends Node, TExprNode { - Expr e; + class Callable = J::Callable; - ExprNode() { this = TExprNode(e) } + AstNode callableGetBody(Callable c) { result = c.getBody() } - override Stmt getEnclosingStmt() { result = e.getEnclosingStmt() } + class Stmt = J::Stmt; - override Callable getEnclosingCallable() { result = e.getEnclosingCallable() } + class Expr = J::Expr; - override ExprParent getAstNode() { result = e } + class BlockStmt = J::BlockStmt; - /** Gets a textual representation of this element. */ - override string toString() { result = e.toString() } + class ExprStmt = J::ExprStmt; - /** Gets the source location for this element. */ - override Location getLocation() { result = e.getLocation() } - } + class IfStmt = J::IfStmt; - /** A control-flow node that represents a statement. */ - class StmtNode extends Node, TStmtNode { - Stmt s; + class LoopStmt = J::LoopStmt; - StmtNode() { this = TStmtNode(s) } + class WhileStmt = J::WhileStmt; - override Stmt getEnclosingStmt() { result = s } + class DoStmt = J::DoStmt; - override Callable getEnclosingCallable() { result = s.getEnclosingCallable() } + class ForStmt = J::ForStmt; - override ExprParent getAstNode() { result = s } + final private class FinalEnhancedForStmt = J::EnhancedForStmt; - override string toString() { result = s.toString() } + class ForeachStmt extends FinalEnhancedForStmt { + Expr getVariable() { result = super.getVariable() } - override Location getLocation() { result = s.getLocation() } + Expr getCollection() { result = super.getExpr() } } - /** A control flow node indicating the normal or exceptional termination of a callable. */ - class AnnotatedExitNode extends Node, TAnnotatedExitNode { - Callable c; - boolean normal; + class BreakStmt extends Stmt { + BreakStmt() { this instanceof J::BreakStmt or this instanceof YieldStmt } + } - AnnotatedExitNode() { this = TAnnotatedExitNode(c, normal) } + class ContinueStmt = J::ContinueStmt; - override Callable getEnclosingCallable() { result = c } + class ReturnStmt = J::ReturnStmt; - override ExprParent getAstNode() { result = c } + class ThrowStmt = J::ThrowStmt; - /** Gets a textual representation of this element. */ - override string toString() { - normal = true and result = "Normal Exit" - or - normal = false and result = "Exceptional Exit" - } + final private class FinalTryStmt = J::TryStmt; - /** Gets the source location for this element. */ - override Location getLocation() { result = c.getLocation() } - } + class TryStmt extends FinalTryStmt { + Stmt getBody() { result = super.getBlock() } - /** A control flow node indicating normal termination of a callable. */ - class NormalExitNode extends AnnotatedExitNode { - NormalExitNode() { this = TAnnotatedExitNode(_, true) } - } + CatchClause getCatch(int index) { result = super.getCatchClause(index) } - /** A control flow node indicating exceptional termination of a callable. */ - class ExceptionalExitNode extends AnnotatedExitNode { - ExceptionalExitNode() { this = TAnnotatedExitNode(_, false) } + Stmt getFinally() { result = super.getFinally() } } - /** A control flow node indicating the termination of a callable. */ - class ExitNode extends Node, TExitNode { - Callable c; + AstNode getTryInit(TryStmt try, int index) { result = try.getResource(index) } - ExitNode() { this = TExitNode(c) } + final private class FinalCatchClause = J::CatchClause; - override Callable getEnclosingCallable() { result = c } + class CatchClause extends FinalCatchClause { + AstNode getVariable() { result = super.getVariable() } - override ExprParent getAstNode() { result = c } + Expr getCondition() { none() } - /** 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() } + Stmt getBody() { result = super.getBlock() } } - /** 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() } + class Switch extends AstNode { + Switch() { + this instanceof SwitchStmt or + this instanceof SwitchExpr or + this instanceof WhenExpr + } - override ExprParent getAstNode() { result = s } + Expr getExpr() { + result = this.(SwitchStmt).getExpr() or + result = this.(SwitchExpr).getExpr() + // WhenExpr is represented with the expression as part of each branch condition + } - /** Gets a textual representation of this element. */ - override string toString() { result = "Assert Throw" } + Case getCase(int index) { + result = this.(SwitchStmt).getCase(index) or + result = this.(SwitchExpr).getCase(index) or + result = this.(WhenExpr).getBranch(index) + } + } - /** Gets the source location for this element. */ - override Location getLocation() { result = s.getLocation() } + int getCaseControlFlowOrder(Switch s, Case c) { + exists(int pos | s.getCase(pos) = c | + // if a default case is not last in the AST, move it last in the CFG order + if c instanceof DefaultCase and exists(s.getCase(pos + 1)) + then result = strictcount(s.getCase(_)) + else result = pos + ) } -} -class ControlFlowNode = ControlFlow::Node; + private Stmt getSwitchStmt(Switch s, int i) { + result = s.(SwitchStmt).getStmt(i) or + result = s.(SwitchExpr).getStmt(i) + } -/** Gets the intra-procedural successor of `n`. */ -private ControlFlowNode succ(ControlFlowNode n) { result = succ(n, _) } + private int numberOfStmts(Switch s) { result = strictcount(getSwitchStmt(s, _)) } -cached -private module ControlFlowGraphImpl { - private import ControlFlow + private predicate caseIndex(Switch s, Case c, int caseIdx, int caseStmtPos) { + c = s.getCase(caseIdx) and + c = getSwitchStmt(s, caseStmtPos) + } - private class AstNode extends ExprParent { - AstNode() { this instanceof Expr or this instanceof Stmt } + class Case extends AstNode { + Case() { this instanceof J::SwitchCase or this instanceof WhenBranch } - Stmt getEnclosingStmt() { - result = this or - result = this.(Expr).getEnclosingStmt() + /** Gets a pattern being matched by this case. */ + AstNode getAPattern() { + result = this.(PatternCase).getAPattern() or + result = this.(ConstCase).getValue(_) + // When branches do not have patterns, only conditions } - 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 + /** Gets the guard expression of this case, if any. */ + Expr getGuard() { + result = this.(PatternCase).getGuard() or result = this.(WhenBranch).getCondition() } /** - * 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`. + * Gets the body element of this case at the specified (zero-based) `index`. + * + * This is either unique when the case has a single right-hand side, or it + * is the sequence of statements between this case and the next case. */ - UncheckedThrowableType getAnUncheckedSubtype() { - result = this + AstNode getBodyElement(int index) { + result = this.(J::SwitchCase).getRuleExpression() and index = 0 or - result instanceof TypeError and this instanceof TypeThrowable + result = this.(J::SwitchCase).getRuleStatement() and index = 0 or - result instanceof TypeRuntimeException and - (this instanceof TypeThrowable or this instanceof TypeException) + result = this.(WhenBranch).getRhs() and index = 0 + or + not this.(J::SwitchCase).isRule() and + exists(Switch s, int caseIdx, int caseStmtPos, int nextCaseStmtPos | + caseIndex(pragma[only_bind_into](s), this, caseIdx, caseStmtPos) and + ( + caseIndex(pragma[only_bind_into](s), _, caseIdx + 1, nextCaseStmtPos) + or + not exists(s.getCase(caseIdx + 1)) and + nextCaseStmtPos = numberOfStmts(s) + ) and + index = [0 .. nextCaseStmtPos - caseStmtPos - 2] and + result = getSwitchStmt(pragma[only_bind_into](s), caseStmtPos + 1 + index) + ) } } - /** - * 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) - ) - } + predicate fallsThrough(Case c) { not c.(J::SwitchCase).isRule() and not c instanceof WhenBranch } - 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*() - ) - ) - } + class ConditionalExpr = J::ConditionalExpr; - /** - * 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) - } + class BinaryExpr = J::BinaryExpr; - /** - * 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) - ) - } + class LogicalAndExpr = AndLogicalExpr; - /** - * 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 - ) - } + class LogicalOrExpr = OrLogicalExpr; - private ThrowableType actualAssertionError() { - result.hasQualifiedName("java.lang", "AssertionError") + class NullCoalescingExpr extends BinaryExpr { + NullCoalescingExpr() { none() } } - 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") + class UnaryExpr = J::UnaryExpr; + + class LogicalNotExpr = LogNotExpr; + + final private class FinalBooleanLiteral = J::BooleanLiteral; + + class BooleanLiteral extends FinalBooleanLiteral { + boolean getValue() { result = this.getBooleanValue() } } +} - /** - * 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) +private module Exceptions { + private predicate methodMayThrow(Method m) { + exists(Stmt stmt | + stmt instanceof ThrowStmt and + not stmt.(ThrowStmt).getParent() = any(Method m0).getBody() or - n instanceof AssertStmt and result = assertionError() + uncheckedExceptionFromMethod(any(MethodCall call | call.getEnclosingStmt() = stmt)) | - n.getEnclosingStmt().getEnclosingStmt+() = try.getBlock() or - n.(Expr).getParent*() = try.getAResource() + stmt.getEnclosingCallable() = m and + not exists(TryStmt try | + exists(try.getACatchClause()) and try.getBlock() = stmt.getEnclosingStmt*() + ) ) } /** - * 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). + * Holds if an unchecked exception may occur in a precondition check or guard wrapper. */ - private predicate mustCatch(CatchClause c, ThrowableType thrown) { - thrown = thrownInBody(c.getTry()) and - hasDescendant(c.getACaughtType(), thrown) + private predicate uncheckedExceptionFromMethod(MethodCall call) { + (methodCallChecksArgument(call) or methodCallUnconditionallyThrows(call)) + or + methodMayThrow(call.getMethod().getSourceDeclaration()) } /** - * 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`. + * Holds if an unchecked exception from `c` may transfer control to a finally + * block inside which `c` is nested. */ - private predicate mayNotCatch(CatchClause c, ThrowableType thrown) { - thrown = thrownInBody(c.getTry()) and - not hasDescendant(c.getACaughtType(), thrown) + private predicate uncheckedExceptionFromFinally(Call c) { + exists(TryStmt try | + c.getEnclosingStmt().getEnclosingStmt+() = try.getBlock() or + c.(Expr).getParent*() = try.getAResource() + | + exists(try.getFinally()) + ) } /** - * 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. + * 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 predicate mayCatch(CatchClause c, ThrowableType thrown) { - mustCatch(c, thrown) - or - mayNotCatch(c, thrown) and exists(c.getACaughtType().commonSubtype(thrown)) + private class UncheckedThrowableSuperType extends RefType { + UncheckedThrowableSuperType() { + this instanceof TypeThrowable or + this instanceof TypeException or + this instanceof UncheckedThrowableType + } } /** - * Given an exception type `thrown`, determine which catch clauses of - * `try` may possibly catch that exception. + * Holds if an unchecked exception from `n` may be caught by an enclosing + * catch clause. */ - 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)) + private predicate uncheckedExceptionFromCatch(Ast::AstNode n) { + exists(TryStmt try, UncheckedThrowableSuperType caught | + n.(Stmt).getEnclosingStmt+() = try.getBlock() or + n.(Expr).getEnclosingStmt().getEnclosingStmt+() = try.getBlock() or + n.(Expr).getParent*() = try.getAResource() + | + try.getACatchClause().getACaughtType() = caught and + ( + caught instanceof TypeClassCastException and n instanceof CastExpr + or + caught instanceof TypeNullPointerException and n instanceof NotNullExpr + or + n instanceof Call + ) ) } /** - * 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, _)`. + * Holds if `n` is expected to possibly throw an exception. This can either + * be due to a declared (likely checked) exception on a call target + * or due to an enclosing try/catch/finally. */ - 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) + predicate mayThrow(Ast::AstNode n) { + exists(n.(Call).getCallee().getAThrownExceptionType()) or - exists(WhenExpr whenexpr | - inBooleanContext(whenexpr) and - whenexpr.getBranch(_).getAResult() = b - ) - or - b = any(PatternCase pc).getGuard() + uncheckedExceptionFromMethod(n) or - inBooleanContext(b.(ExprStmt).getExpr()) + uncheckedExceptionFromFinally(n) or - inBooleanContext(b.(StmtExpr).getStmt()) + uncheckedExceptionFromCatch(n) } +} +private module NonReturningCalls { /** * 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 @@ -602,7 +381,7 @@ private module ControlFlowGraphImpl { /** * Gets a `MethodCall` that always throws an exception or calls `exit`. */ - private MethodCall nonReturningMethodCall() { + MethodCall nonReturningMethodCall() { methodCallUnconditionallyThrows(result) or result.getMethod().getSourceDeclaration() = nonReturningMethod() or result = likelyNonReturningMethod().getAnAccess() @@ -644,1142 +423,212 @@ private module ControlFlowGraphImpl { ) ) } +} - // 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) - } +private module Input implements InputSig1, InputSig2 { + private import java as J - /** - * 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]) + /** Holds if this catch clause catches all exceptions. */ + predicate catchAll(Ast::CatchClause catch) { + catch.getACaughtType() instanceof TypeThrowable + or + exists(TryStmt try, int last | + // not Exceptions::expectUncaught(try) and + catch.getACaughtType() instanceof TypeException and + try.getCatchClause(last) = catch and + not exists(try.getCatchClause(last + 1)) ) } - /** - * 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() + /** Holds if this case matches all possible values. */ + predicate matchAll(Ast::Case c) { + c instanceof DefaultCase + or + // Switch expressions must be exhaustive, so the last case matches all remaining values + exists(SwitchExpr switchExpr, int last | + switchExpr.getCase(last) = c and + not exists(switchExpr.getCase(last + 1)) ) - } - - 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, _) + c.(J::PatternCase).getAPattern().getType() instanceof TypeObject + or + // When branches do not have patterns, only conditions + c instanceof WhenBranch } - /** - * 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 - } + private newtype TLabel = + TJavaLabel(string l) { exists(LabeledStmt lbl | l = lbl.getLabel()) } or + TYield() - /** 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).getOperand() - 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 + class Label extends TLabel { + string toString() { + exists(string l | this = TJavaLabel(l) and result = l) 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).getExpr() - 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() + this = TYield() and result = "yield" } } - /** - * 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)) + private Label getLabelOfLoop(Stmt s) { + exists(LabeledStmt l | s = l.getStmt() | + result = TJavaLabel(l.getLabel()) or + result = getLabelOfLoop(l) ) } - /** - * 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 + predicate hasLabel(Ast::AstNode n, Label l) { + l = getLabelOfLoop(n) or - result.asExpr() = n and n instanceof StmtExpr + l = TJavaLabel(n.(BreakStmt).getLabel()) or - result = n.getCfgNode() and n.(PostOrderNode).isLeafNode() + l = TJavaLabel(n.(ContinueStmt).getLabel()) or - result = first(n.(PostOrderNode).firstChild()) + l = TYield() and n instanceof YieldStmt 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 + l = TYield() 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(_) + predicate inConditionalContext(Ast::AstNode n, ConditionKind kind) { + kind.isBoolean() and + any(AssertStmt assertstmt).getExpr() = n } - /** - * 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(_) - ) + predicate postOrInOrder(Ast::AstNode n) { + // expressions are already post-order, but we need the calls that are statements to be post-order as well + n instanceof Call + or + n instanceof SynchronizedStmt } - /** - * 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 string assertThrowNodeTag() { result = "[assert-throw]" } - private predicate lastInFinally(TryStmt try, Node last) { - last(try.getFinally(), last, NormalCompletion()) - } + private string instanceofTrueNodeTag() { result = "[instanceof-true]" } - 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 - ) - ) + predicate additionalNode(Ast::AstNode n, string tag, NormalSuccessor t) { + n instanceof AssertStmt and tag = assertThrowNodeTag() and t instanceof DirectSuccessor + or + n.(InstanceOfExpr).isPattern() and + tag = instanceofTrueNodeTag() and + t.(BooleanSuccessor).getValue() = true } /** - * 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`. + * Holds if `ast` may result in an abrupt completion `c` originating at + * `n`. The boolean `always` indicates whether the abrupt completion + * always occurs or whether `n` may also terminate normally. */ - 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).getOperand(), 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) - ) + predicate beginAbruptCompletion( + Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always + ) { + ast instanceof AssertStmt and + n.isAdditional(ast, assertThrowNodeTag()) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = true 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 - ) - ) - ) - ) + Exceptions::mayThrow(ast) and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = false 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) - ) - ) + ast = NonReturningCalls::nonReturningMethodCall() and + n.isIn(ast) and + c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and + always = true } /** - * Compute the intra-procedural successors of cfg node `n`, assuming its - * execution finishes with the given completion. + * Holds if an abrupt completion `c` from within `ast` is caught with + * flow continuing at `n`. */ - 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).getOperand()) 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)) + predicate endAbruptCompletion(Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c) { + exists(LabeledStmt lbl | + ast = lbl.getStmt() and + n.isAfter(lbl) and + c.getSuccessorType() instanceof BreakSuccessor and + c.hasLabel(TJavaLabel(lbl.getLabel())) ) - 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.getBody()) - | - // 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.getBody()) - 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.getBody(), 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.getBody()) and - completion = NormalCompletion() - or - // Finally, the back edge of the loop goes to reassign the variable. - last(for.getBody(), 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).getBody()) 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() - ) + } + + /** Holds if there is a local non-abrupt step from `n1` to `n2`. */ + predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(InstanceOfExpr ioe | + // common + n1.isBefore(ioe) and + n2.isBefore(ioe.getExpr()) 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, _)) - ) + n1.isAfter(ioe.getExpr()) and + n2.isIn(ioe) 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()) + // std postorder: + not ioe.isPattern() and + n1.isIn(ioe) and + n2.isAfter(ioe) or - last(pc.getAPattern(), n, completion) and - completion = NormalCompletion() and - result = first(pc.getGuard()) + // pattern case: + ioe.isPattern() and + n1.isIn(ioe) and + n2.isAfterValue(ioe, any(BooleanSuccessor s | s.getValue() = false)) 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 + n1.isIn(ioe) and + n2.isAdditional(ioe, instanceofTrueNodeTag()) 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)) + n1.isAdditional(ioe, instanceofTrueNodeTag()) and + n2.isBefore(ioe.getPattern()) or - exists(int i | last(s.getVariable(i), n, completion) and result = first(s.getVariable(i + 1))) + n1.isAfter(ioe.getPattern()) and + n2.isAfterValue(ioe, any(BooleanSuccessor s | s.getValue() = true)) ) or - // Assert statements: exists(AssertStmt assertstmt | - last(assertstmt.getExpr(), n, completion) and - completion = BooleanCompletion(true, _) and - result.asStmt() = assertstmt + n1.isBefore(assertstmt) and + n2.isBefore(assertstmt.getExpr()) or - last(assertstmt.getExpr(), n, completion) and - completion = BooleanCompletion(false, _) and + n1.isAfterValue(assertstmt.getExpr(), any(BooleanSuccessor s | s.getValue() = true)) and + n2.isAfter(assertstmt) + or + n1.isAfterValue(assertstmt.getExpr(), any(BooleanSuccessor s | s.getValue() = false)) and ( - result = first(assertstmt.getMessage()) + n2.isBefore(assertstmt.getMessage()) or not exists(assertstmt.getMessage()) and - result.(AssertThrowNode).getAstNode() = assertstmt + n2.isAdditional(assertstmt, assertThrowNodeTag()) ) or - last(assertstmt.getMessage(), n, completion) and - completion = NormalCompletion() and - result.(AssertThrowNode).getAstNode() = assertstmt + n1.isAfter(assertstmt.getMessage()) and + n2.isAdditional(assertstmt, assertThrowNodeTag()) ) or - // When expressions: - exists(WhenExpr whenexpr | - n.asExpr() = whenexpr and - result = first(whenexpr.getBranch(0)) and - completion = NormalCompletion() + // Synchronized statements execute their expression _before_ synchronization, so the CFG reflects that. + exists(SynchronizedStmt synch | + n1.isBefore(synch) and + n2.isBefore(synch.getExpr()) 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()) + n1.isAfter(synch.getExpr()) and + n2.isIn(synch) 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) + n1.isIn(synch) and + n2.isBefore(synch.getBlock()) + or + n1.isAfter(synch.getBlock()) and + n2.isAfter(synch) ) } - - /** 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, _)) } +/** A control-flow node that branches based on a boolean condition. */ +class ConditionNode extends ControlFlowNode { + ConditionNode() { exists(this.getASuccessor(any(BooleanSuccessor t))) } /** Gets a true- or false-successor of the `ConditionNode`. */ - ControlFlow::Node getABranchSuccessor(boolean branch) { result = branchSuccessor(this, branch) } + ControlFlowNode getABranchSuccessor(boolean branch) { + result = this.getASuccessor(any(BooleanSuccessor t | t.getValue() = branch)) + } /** Gets a true-successor of the `ConditionNode`. */ - ControlFlow::Node getATrueSuccessor() { result = this.getABranchSuccessor(true) } + ControlFlowNode getATrueSuccessor() { result = this.getABranchSuccessor(true) } /** Gets a false-successor of the `ConditionNode`. */ - ControlFlow::Node getAFalseSuccessor() { result = this.getABranchSuccessor(false) } + ControlFlowNode getAFalseSuccessor() { result = this.getABranchSuccessor(false) } /** Gets the condition of this `ConditionNode`. */ ExprParent getCondition() { result = this.asExpr() or result = this.asStmt() } } - -private import codeql.controlflow.PrintGraph as PrintGraph - -private module PrintGraphInput implements PrintGraph::InputSig { - private import java as J - - class Callable = J::Callable; - - class ControlFlowNode = J::ControlFlowNode; - - ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t) { result = n.getASuccessor(t) } -} - -import PrintGraph::PrintGraph diff --git a/java/ql/lib/semmle/code/java/controlflow/BasicBlocks.qll b/java/ql/lib/semmle/code/java/controlflow/BasicBlocks.qll index f214cbbd0b1a..a5229bea590b 100644 --- a/java/ql/lib/semmle/code/java/controlflow/BasicBlocks.qll +++ b/java/ql/lib/semmle/code/java/controlflow/BasicBlocks.qll @@ -6,151 +6,8 @@ module; import java import Dominance -private import codeql.controlflow.BasicBlock as BB -private import codeql.controlflow.SuccessorType - -private module Input implements BB::InputSig { - /** Hold if `t` represents a conditional successor type. */ - predicate successorTypeIsCondition(SuccessorType t) { none() } - - /** A delineated part of the AST with its own CFG. */ - class CfgScope = Callable; - - /** The class of control flow nodes. */ - class Node = ControlFlowNode; - - /** Gets the CFG scope in which this node occurs. */ - CfgScope nodeGetCfgScope(Node node) { node.getEnclosingCallable() = result } - - /** Gets an immediate successor of this node. */ - Node nodeGetASuccessor(Node node, SuccessorType t) { result = node.getASuccessor(t) } - - /** - * Holds if `node` represents an entry node to be used when calculating - * dominance. - */ - predicate nodeIsDominanceEntry(Node node) { - exists(Stmt entrystmt | entrystmt = node.asStmt() | - exists(Callable c | entrystmt = c.getBody()) - or - // This disjunct is technically superfluous, but safeguards against extractor problems. - entrystmt instanceof BlockStmt and - not exists(entrystmt.getEnclosingCallable()) and - not entrystmt.getParent() instanceof Stmt - ) - } - - /** - * Holds if `node` represents an exit node to be used when calculating - * post dominance. - */ - predicate nodeIsPostDominanceExit(Node node) { node instanceof ControlFlow::NormalExitNode } -} - -private module BbImpl = BB::Make; - -import BbImpl - -/** Holds if the dominance relation is calculated for `bb`. */ -predicate hasDominanceInformation(BasicBlock bb) { - exists(BasicBlock entry | - Input::nodeIsDominanceEntry(entry.getFirstNode()) and entry.getASuccessor*() = bb - ) -} - -/** - * A basic block, that is, a maximal straight-line sequence of control flow nodes - * without branches or joins. - */ -class BasicBlock extends BbImpl::BasicBlock { - /** Gets the immediately enclosing callable whose body contains this node. */ - Callable getEnclosingCallable() { result = this.getScope() } - - /** - * Holds if this basic block dominates basic block `bb`. - * - * That is, all paths reaching `bb` from the entry point basic block must - * go through this basic block. - */ - predicate dominates(BasicBlock bb) { super.dominates(bb) } - - /** - * Holds if this basic block strictly dominates basic block `bb`. - * - * That is, all paths reaching `bb` from the entry point basic block must - * go through this basic block and this basic block is different from `bb`. - */ - predicate strictlyDominates(BasicBlock bb) { super.strictlyDominates(bb) } - - /** Gets an immediate successor of this basic block of a given type, if any. */ - BasicBlock getASuccessor(SuccessorType t) { result = super.getASuccessor(t) } - - BasicBlock getASuccessor() { result = super.getASuccessor() } - - BasicBlock getImmediateDominator() { result = super.getImmediateDominator() } - - predicate inDominanceFrontier(BasicBlock df) { super.inDominanceFrontier(df) } - - predicate strictlyPostDominates(BasicBlock bb) { super.strictlyPostDominates(bb) } - - predicate postDominates(BasicBlock bb) { super.postDominates(bb) } - - /** - * DEPRECATED: Use `getASuccessor` instead. - * - * Gets an immediate successor of this basic block. - */ - deprecated BasicBlock getABBSuccessor() { result = this.getASuccessor() } - - /** - * DEPRECATED: Use `getAPredecessor` instead. - * - * Gets an immediate predecessor of this basic block. - */ - deprecated BasicBlock getABBPredecessor() { result.getASuccessor() = this } - - /** - * DEPRECATED: Use `strictlyDominates` instead. - * - * Holds if this basic block strictly dominates `node`. - */ - deprecated predicate bbStrictlyDominates(BasicBlock node) { this.strictlyDominates(node) } - - /** - * DEPRECATED: Use `dominates` instead. - * - * Holds if this basic block dominates `node`. (This is reflexive.) - */ - deprecated predicate bbDominates(BasicBlock node) { this.dominates(node) } - - /** - * DEPRECATED: Use `strictlyPostDominates` instead. - * - * Holds if this basic block strictly post-dominates `node`. - */ - deprecated predicate bbStrictlyPostDominates(BasicBlock node) { this.strictlyPostDominates(node) } - - /** - * DEPRECATED: Use `postDominates` instead. - * - * Holds if this basic block post-dominates `node`. (This is reflexive.) - */ - deprecated predicate bbPostDominates(BasicBlock node) { this.postDominates(node) } -} /** A basic block that ends in an exit node. */ class ExitBlock extends BasicBlock { ExitBlock() { this.getLastNode() instanceof ControlFlow::ExitNode } } - -private class BasicBlockAlias = BasicBlock; - -module Cfg implements BB::CfgSig { - class ControlFlowNode = BbImpl::ControlFlowNode; - - class BasicBlock = BasicBlockAlias; - - class EntryBasicBlock extends BasicBlock instanceof BbImpl::EntryBasicBlock { } - - predicate dominatingEdge(BasicBlock bb1, BasicBlock bb2) { BbImpl::dominatingEdge(bb1, bb2) } -} diff --git a/java/ql/lib/semmle/code/java/controlflow/Paths.qll b/java/ql/lib/semmle/code/java/controlflow/Paths.qll index 23f0786966db..7cd4380a4c51 100644 --- a/java/ql/lib/semmle/code/java/controlflow/Paths.qll +++ b/java/ql/lib/semmle/code/java/controlflow/Paths.qll @@ -66,10 +66,6 @@ private class JoinBlock extends BasicBlock { JoinBlock() { 2 <= strictcount(this.getAPredecessor()) } } -private class ReachableBlock extends BasicBlock { - ReachableBlock() { hasDominanceInformation(this) } -} - /** * Holds if `bb` is a block that is collectively dominated by a set of one or * more actions that individually does not dominate the exit. @@ -78,7 +74,7 @@ private predicate postActionBlock(BasicBlock bb, ActionConfiguration conf) { bb = nonDominatingActionBlock(conf) or if bb instanceof JoinBlock - then forall(ReachableBlock pred | pred = bb.getAPredecessor() | postActionBlock(pred, conf)) + then forall(BasicBlock pred | pred = bb.getAPredecessor() | postActionBlock(pred, conf)) else postActionBlock(bb.getAPredecessor(), conf) } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll b/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll index 962ff0bc169d..07d871ab9bc8 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/BaseSSA.qll @@ -93,8 +93,7 @@ private module BaseSsaImpl { /** Holds if `n` updates the local variable `v`. */ predicate variableUpdate(BaseSsaSourceVariable v, ControlFlowNode n, BasicBlock b, int i) { exists(VariableUpdate a | a.getControlFlowNode() = n | getDestVar(a) = v) and - b.getNode(i) = n and - hasDominanceInformation(b) + b.getNode(i) = n } /** Gets the definition point of a nested class in the parent scope. */ @@ -178,15 +177,12 @@ private module SsaImplInput implements SsaImplCommon::InputSig { + /** An AST node. */ + class AstNode { + /** Gets a textual representation of this AST node. */ + string toString(); + + /** Gets the location of this AST node. */ + Location getLocation(); + } + + /** Gets the child of this AST node at the specified index. */ + AstNode getChild(AstNode n, int index); + + /** Gets the immediately enclosing callable that contains this node. */ + Callable getEnclosingCallable(AstNode node); + + class Callable extends AstNode; + + /** Gets the body of this callable, if any. */ + AstNode callableGetBody(Callable c); + + /** A statement. */ + class Stmt extends AstNode; + + /** An expression. */ + class Expr extends AstNode; + + class BlockStmt extends Stmt { + /** Gets the `n`th (zero-based) statement in this block. */ + Stmt getStmt(int n); + + Stmt getLastStmt(); + } + + class ExprStmt extends Stmt { + /** Gets the expression in this expression statement. */ + Expr getExpr(); + } + + class IfStmt extends Stmt { + /** Gets the condition of this if statement. */ + Expr getCondition(); + + /** Gets the `then` (true) branch of this `if` statement. */ + Stmt getThen(); + + /** Gets the `else` (false) branch of this `if` statement, if any. */ + Stmt getElse(); + } + + class LoopStmt extends Stmt { + /** Gets the body of this loop statement. */ + Stmt getBody(); + } + + class WhileStmt extends LoopStmt { + /** Gets the boolean condition of this `while` loop. */ + Expr getCondition(); + } + + class DoStmt extends LoopStmt { + /** Gets the boolean condition of this `do-while` loop. */ + Expr getCondition(); + } + + /** A traditional C-style `for` loop. */ + class ForStmt extends LoopStmt { + /** Gets the initializer expression of the loop at the specified (zero-based) position, if any. */ + Expr getInit(int index); + + /** Gets the boolean condition of this `for` loop. */ + Expr getCondition(); + + /** Gets the update expression of this loop at the specified (zero-based) position, if any. */ + Expr getUpdate(int index); + } + + class ForeachStmt extends LoopStmt { + /** Gets the variable declaration of this `foreach` loop. */ + Expr getVariable(); + + /** Gets the collection expression of this `foreach` loop. */ + Expr getCollection(); + } + + class BreakStmt extends Stmt; + + class ContinueStmt extends Stmt; + + class ReturnStmt extends Stmt { + /** Gets the expression being returned, if any. */ + Expr getExpr(); + } + + class ThrowStmt extends Stmt { + /** Gets the expression being thrown. */ + Expr getExpr(); + } + + class TryStmt extends Stmt { + /** Gets the body of this `try` statement. */ + Stmt getBody(); + + /** + * Gets the `catch` clause at the specified (zero-based) position `index` + * in this `try` statement. + */ + CatchClause getCatch(int index); + + /** Gets the `finally` block of this `try` statement, if any. */ + Stmt getFinally(); + } + + /** + * Gets the initializer of this `try` statement at the specified (zero-based) + * position `index`, if any. + * + * An example of this are resource declarations in Java's try-with-resources + * statement. + */ + default AstNode getTryInit(TryStmt try, int index) { none() } + + /** + * Gets the `else` block of this `try` statement, if any. + * + * Only some languages (e.g. Python) support `try-else` constructs. + */ + default AstNode getTryElse(TryStmt try) { none() } + + class CatchClause extends AstNode { + AstNode getVariable(); + + Expr getCondition(); + + /** Gets the body of this catch clause. */ + Stmt getBody(); + } + + class Switch extends AstNode { + /** + * Gets the expression being switched on. + * + * In some languages this is optional, in which case this predicate then + * might not hold. + */ + Expr getExpr(); + + /** Gets the case at the specified (zero-based) `index`. */ + Case getCase(int index); + } + + /** + * Gets an integer indicating the control flow order of a case within a switch. + * This is most often the same as the AST order, but can be different in some + * languages if the language allows a default case to appear before other + * cases. + */ + default int getCaseControlFlowOrder(Switch s, Case c) { s.getCase(result) = c } + + class Case extends AstNode { + /** Gets a pattern being matched by this case. */ + AstNode getAPattern(); + + /** Gets the guard expression of this case, if any. */ + Expr getGuard(); + + /** + * Gets the body element of this case at the specified (zero-based) `index`. + * + * This is either unique when the case has a single right-hand side, or it + * is the sequence of statements between this case and the next case. + */ + AstNode getBodyElement(int index); + } + + /** + * Holds if this case can fall through to the next case if it is not + * otherwise prevented with a `break` or similar. + */ + default predicate fallsThrough(Case c) { none() } + + class ConditionalExpr extends Expr { + /** Gets the condition of this expression. */ + Expr getCondition(); + + /** Gets the true branch of this expression. */ + Expr getThen(); + + /** Gets the false branch of this expression. */ + Expr getElse(); + } + + class BinaryExpr extends Expr { + /** Gets the left operand of this binary expression. */ + Expr getLeftOperand(); + + /** Gets the right operand of this binary expression. */ + Expr getRightOperand(); + } + + class LogicalAndExpr extends BinaryExpr; + + class LogicalOrExpr extends BinaryExpr; + + class NullCoalescingExpr extends BinaryExpr; + + class UnaryExpr extends Expr { + /** Gets the operand of this unary expression. */ + Expr getOperand(); + } + + class LogicalNotExpr extends UnaryExpr; + + class BooleanLiteral extends Expr { + /** Gets the boolean value of this literal. */ + boolean getValue(); + } +} + +module Make0 Ast> { + private import Ast + + signature module InputSig1 { + /** + * A label used for matching jump sources and targets, for example in goto + * statements. + */ + class Label { + string toString(); + } + + /** + * Holds if the node `n` has the label `l`. For example, a label in a goto + * statement or a goto target. + */ + default predicate hasLabel(AstNode n, Label l) { none() } + + /** + * Holds if the value of `child` is propagated to `parent`. For example, + * the right-hand side of short-circuiting expressions. + */ + default predicate propagatesValue(AstNode child, AstNode parent) { none() } + + /** + * Holds if `n` is in a conditional context of kind `kind`. For example, + * the left-hand side of a short-circuiting `&&` expression is in a + * boolean conditional context. + */ + default predicate inConditionalContext(AstNode n, ConditionKind kind) { none() } + + /** + * Holds if `n` is executed in post-order or in-order. This means that an + * additional node is created to represent `n` in the control flow graph. + * Otherwise, `n` is represented by the "before" node. + */ + default predicate postOrInOrder(AstNode n) { none() } + + /** + * Holds if an additional node tagged with `tag` should be created for + * `n`. Edges targeting such nodes are labeled with `t` and therefore `t` + * should be unique for a given `(n,tag)` pair. + */ + default predicate additionalNode(AstNode n, string tag, NormalSuccessor t) { none() } + + /** + * Holds if `t1` implies `t2`. + * + * For example, in JavaScript, true (truthy) implies not-null, and null implies false (falsy). + */ + default predicate successorValueImplies(ConditionalSuccessor t1, ConditionalSuccessor t2) { + none() + } + } + + module Make1 { + private predicate postOrInOrder(AstNode n) { + Input1::postOrInOrder(n) + or + n instanceof ReturnStmt + or + n instanceof ThrowStmt + or + n instanceof BreakStmt + or + n instanceof ContinueStmt + or + n instanceof Expr and + exists(getChild(n, _)) and + not n instanceof LogicalAndExpr and + not n instanceof LogicalOrExpr and + not n instanceof NullCoalescingExpr and + not n instanceof LogicalNotExpr and + not n instanceof ConditionalExpr and + not n instanceof Switch and + not n instanceof Case + } + + private predicate shortCircuiting(BinaryExpr expr, ConditionalSuccessor shortcircuitValue) { + expr instanceof LogicalAndExpr and shortcircuitValue.(BooleanSuccessor).getValue() = false + or + expr instanceof LogicalOrExpr and shortcircuitValue.(BooleanSuccessor).getValue() = true + or + expr instanceof NullCoalescingExpr and shortcircuitValue.(NullnessSuccessor).getValue() = true + } + + /** + * Holds if the value of `child` is propagated to `parent`. For example, + * the right-hand side of short-circuiting expressions. + */ + private predicate propagatesValue(AstNode child, AstNode parent) { + Input1::propagatesValue(child, parent) + or + shortCircuiting(parent, _) and + not postOrInOrder(parent) and + parent.(BinaryExpr).getRightOperand() = child + or + parent.(ConditionalExpr).getThen() = child + or + parent.(ConditionalExpr).getElse() = child + or + parent.(BlockStmt).getLastStmt() = child + or + parent.(ExprStmt).getExpr() = child + } + + /** + * Holds if `n` is in a conditional context of kind `kind`. For example, + * the left-hand side of a short-circuiting `&&` expression is in a + * boolean conditional context. + */ + private predicate inConditionalContext(AstNode n, ConditionKind kind) { + Input1::inConditionalContext(n, kind) + or + exists(AstNode parent | + propagatesValue(n, parent) and + inConditionalContext(parent, kind) + ) + or + exists(LogicalNotExpr notexpr | + n = notexpr.getOperand() and + inConditionalContext(notexpr, kind) and + kind.isBoolean() + ) + or + exists(BinaryExpr binexpr, ConditionalSuccessor shortcircuitValue | + shortCircuiting(binexpr, shortcircuitValue) and + n = binexpr.getLeftOperand() and + kind = shortcircuitValue.getKind() + ) + or + kind.isBoolean() and + ( + any(IfStmt ifstmt).getCondition() = n or + any(WhileStmt whilestmt).getCondition() = n or + any(DoStmt dostmt).getCondition() = n or + any(ForStmt forstmt).getCondition() = n or + any(ConditionalExpr condexpr).getCondition() = n or + any(CatchClause catch).getCondition() = n or + any(Case case).getGuard() = n + ) + or + any(ForeachStmt foreachstmt).getCollection() = n and kind.isEmptiness() + or + n instanceof CatchClause and kind.isMatching() + or + n instanceof Case and kind.isMatching() + } + + /** + * Holds if `n` is a simple leaf node in the AST that does not appear in a + * conditional context. For such nodes, there is no need to create separate + * "before" and "after" control flow nodes, so we merge them. + */ + private predicate simpleLeafNode(AstNode n) { + not exists(getChild(n, _)) and + not postOrInOrder(n) and + not inConditionalContext(n, _) + } + + private string loopHeaderTag() { result = "[LoopHeader]" } + + private predicate additionalNode(AstNode n, string tag, NormalSuccessor t) { + Input1::additionalNode(n, tag, t) + or + n instanceof LoopStmt and + tag = loopHeaderTag() and + t instanceof DirectSuccessor + } + + /** + * Holds if `n` cannot terminate normally. For these cases there is no + * need to create an "after" node as that would be unreachable. + * Furthermore, skipping these nodes improves precision slightly for + * finally blocks, as the corresponding try blocks are otherwise generally + * assumed to be able to terminate normally, and hence allows for + * a normal successor from the finally block. + */ + private predicate cannotTerminateNormally(AstNode n) { + n instanceof BreakStmt + or + n instanceof ContinueStmt + or + n instanceof ReturnStmt + or + n instanceof ThrowStmt + or + cannotTerminateNormally(n.(BlockStmt).getLastStmt()) + or + cannotTerminateNormally(n.(ExprStmt).getExpr()) + or + exists(IfStmt ifstmt | + ifstmt = n and + cannotTerminateNormally(ifstmt.getThen()) and + cannotTerminateNormally(ifstmt.getElse()) + ) + or + exists(TryStmt trystmt | + trystmt = n and + cannotTerminateNormally(trystmt.getBody()) and + forall(CatchClause catch | trystmt.getCatch(_) = catch | + cannotTerminateNormally(catch.getBody()) + ) + ) + } + + /* + * - Every AST node has "before" and "after" control flow nodes (except simple leaf nodes). + * - CFG snippets always start at the "before" node. + * - In case of normal termination, the final node is an "after" node. + * - Boolean and other conditional completions are encoded in the "after" nodes. + * - The number of "after" nodes for a given AST node depends on whether the AST + * node is in a conditional context. + * - Successors are specified as simple steps between control flow nodes for + * NormalSuccessors, and as pairs of half-edges for AbruptSuccessors. This + * allows all specifications to be local. + * - Every AST node has a unique control flow node representing it. For + * preorder this is the "before" node, and for inorder/postorder this is an + * additional node that typically sits just before "after" (but may or may + * not step to it, since "after" represents normal termination). + */ + + private newtype TNode = + TBeforeNode(AstNode n) { exists(getEnclosingCallable(n)) } or + TAstNode(AstNode n) { postOrInOrder(n) } or + TAfterValueNode(AstNode n, ConditionalSuccessor t) { inConditionalContext(n, t.getKind()) } or + TAfterNode(AstNode n) { + exists(getEnclosingCallable(n)) and + not inConditionalContext(n, _) and + not cannotTerminateNormally(n) and + not simpleLeafNode(n) + } or + TAdditionalNode(AstNode n, string tag) { additionalNode(n, tag, _) } or + TEntryNode(Callable c) { exists(callableGetBody(c)) } or + TAnnotatedExitNode(Callable c, Boolean normal) { exists(callableGetBody(c)) } or + TExitNode(Callable c) { exists(callableGetBody(c)) } + + private class NodeImpl extends TNode { + /** + * Holds if this is the node representing the point in the control flow + * before the execution of `n`. + */ + predicate isBefore(AstNode n) { this = TBeforeNode(n) } + + /** + * Holds if this is a node representing the point in the control flow + * after the normal termination of `n`. For simple leaf nodes, this is + * merged with the "before" node and is hence equal to it. For nodes in + * conditional contexts, this may be one of two possible "after" nodes + * representing the different possible values of `n`. + */ + predicate isAfter(AstNode n) { + this = TAfterNode(n) + or + this = TAfterValueNode(n, _) + or + this = TBeforeNode(n) and simpleLeafNode(n) + } + + /** + * Holds if this is the node representing the normal termination of `n` + * with the value `t`. + * + * Note that `n`, and most importantly `t`, must be bound, and if this + * predicate is used to identify the starting point of a step, then + * `inConditionalContext(n, t.getKind())` must hold. On the other hand, if + * this is used to identify the end point of a step, then there is no + * such requirement - in that case `t` will be translated to the + * appropriate `SuccessorType` for `n`. + */ + bindingset[n, t] + predicate isAfterValue(AstNode n, ConditionalSuccessor t) { + this = TAfterNode(n) and exists(t) + or + this = TBeforeNode(n) and simpleLeafNode(n) and exists(t) + or + this = TAfterValueNode(n, t) + or + exists(ConditionalSuccessor t0 | this = TAfterValueNode(n, t0) | + Input1::successorValueImplies(t, t0) + or + not Input1::successorValueImplies(t, _) and + t.getKind() != t0.getKind() + ) + } + + /** + * Holds if this is the node representing the evaluation of `n` to the + * value `true`. + * + * Note that if this predicate is used to identify the starting point of + * a step, then `inConditionalContext(n, BooleanCondition())` must hold. + * On the other hand, if this is used to identify the end point of a + * step, then there is no such requirement. + */ + predicate isAfterTrue(AstNode n) { + this.isAfterValue(n, any(BooleanSuccessor b | b.getValue() = true)) + } + + /** + * Holds if this is the node representing the evaluation of `n` to the + * value `false`. + * + * Note that if this predicate is used to identify the starting point of + * a step, then `inConditionalContext(n, BooleanCondition())` must hold. + * On the other hand, if this is used to identify the end point of a + * step, then there is no such requirement. + */ + predicate isAfterFalse(AstNode n) { + this.isAfterValue(n, any(BooleanSuccessor b | b.getValue() = false)) + } + + /** + * Holds if this is the node representing the given AST node when `n` + * has an in-order or post-order execution. + */ + predicate isIn(AstNode n) { this = TAstNode(n) } + + /** + * Holds if this is an additional control flow node with the given tag + * for the given AST node. + */ + predicate isAdditional(AstNode n, string tag) { this = TAdditionalNode(n, tag) } + + /** + * Holds if this is the unique control flow node that represents the + * given AST node. + */ + predicate injects(AstNode n) { + if postOrInOrder(n) then this = TAstNode(n) else this = TBeforeNode(n) + } + + /** Gets the statement this control flow node uniquely represents, if any. */ + Stmt asStmt() { this.injects(result) } + + /** Gets the expression this control flow node uniquely represents, if any. */ + Expr asExpr() { this.injects(result) } + + /** Gets the enclosing callable of this control flow node. */ + Callable getEnclosingCallable() { result = getEnclosingCallable(this.getAstNode()) } + + /** Gets the AST node with which this control flow node is associated. */ + abstract AstNode getAstNode(); + + /** Gets a textual representation of this node. */ + abstract string toString(); + + /** Gets the source location for this node. */ + Location getLocation() { result = this.getAstNode().getLocation() } + } + + final class PreControlFlowNode = NodeImpl; + + private class BeforeNode extends NodeImpl, TBeforeNode { + private AstNode n; + + BeforeNode() { this = TBeforeNode(n) } + + override AstNode getAstNode() { result = n } + + override string toString() { + if postOrInOrder(n) then result = "Before " + n.toString() else result = n.toString() + } + } + + private class MidNode extends NodeImpl, TAstNode { + private AstNode n; + + MidNode() { this = TAstNode(n) } + + override AstNode getAstNode() { result = n } + + override string toString() { result = n.toString() } + } + + private class AfterValueNode extends NodeImpl, TAfterValueNode { + private AstNode n; + private ConditionalSuccessor t; + + AfterValueNode() { this = TAfterValueNode(n, t) } + + override AstNode getAstNode() { result = n } + + override string toString() { result = "After " + n.toString() + " [" + t.toString() + "]" } + } + + private class AfterNode extends NodeImpl, TAfterNode { + private AstNode n; + + AfterNode() { this = TAfterNode(n) } + + override AstNode getAstNode() { result = n } + + override string toString() { result = "After " + n.toString() } + } + + private class AdditionalNode extends NodeImpl, TAdditionalNode { + private AstNode n; + private string tag; + + AdditionalNode() { this = TAdditionalNode(n, tag) } + + override AstNode getAstNode() { result = n } + + NormalSuccessor getSuccessorType() { additionalNode(n, tag, result) } + + override string toString() { result = tag + " " + n.toString() } + } + + final private class EntryNodeImpl extends NodeImpl, TEntryNode { + private Callable c; + + EntryNodeImpl() { this = TEntryNode(c) } + + override Callable getEnclosingCallable() { result = c } + + override AstNode getAstNode() { result = c } + + override string toString() { result = "Entry" } + } + + /** A control flow node indicating the normal or exceptional termination of a callable. */ + final private class AnnotatedExitNodeImpl extends NodeImpl, TAnnotatedExitNode { + Callable c; + boolean normal; + + AnnotatedExitNodeImpl() { this = TAnnotatedExitNode(c, normal) } + + override Callable getEnclosingCallable() { result = c } + + override AstNode getAstNode() { result = c } + + override string toString() { + normal = true and result = "Normal Exit" + or + normal = false and result = "Exceptional Exit" + } + } + + /** A control flow node indicating normal termination of a callable. */ + final private class NormalExitNodeImpl extends AnnotatedExitNodeImpl { + NormalExitNodeImpl() { this = TAnnotatedExitNode(_, true) } + } + + /** A control flow node indicating exceptional termination of a callable. */ + final private class ExceptionalExitNodeImpl extends AnnotatedExitNodeImpl { + ExceptionalExitNodeImpl() { this = TAnnotatedExitNode(_, false) } + } + + /** A control flow node indicating the termination of a callable. */ + final private class ExitNodeImpl extends NodeImpl, TExitNode { + Callable c; + + ExitNodeImpl() { this = TExitNode(c) } + + override Callable getEnclosingCallable() { result = c } + + override AstNode getAstNode() { result = c } + + override string toString() { result = "Exit" } + } + + private newtype TAbruptCompletion = + TSimpleAbruptCompletion(AbruptSuccessor t) or + TLabeledAbruptCompletion(JumpSuccessor t, Input1::Label l) + + /** + * A value indicating an abrupt completion of an AST node in the control + * flow graph. This is mostly equivalent to an AbruptSuccessor, but may + * also carry a label to, for example, link a goto with its target. + */ + class AbruptCompletion extends TAbruptCompletion { + string toString() { + exists(AbruptSuccessor t | this = TSimpleAbruptCompletion(t) and result = t.toString()) + or + exists(AbruptSuccessor t, Input1::Label l | + this = TLabeledAbruptCompletion(t, l) and + result = t.toString() + " " + l.toString() + ) + } + + AbruptSuccessor getSuccessorType() { + this = TSimpleAbruptCompletion(result) or this = TLabeledAbruptCompletion(result, _) + } + + AbruptSuccessor asSimpleAbruptCompletion() { this = TSimpleAbruptCompletion(result) } + + predicate hasLabel(Input1::Label l) { this = TLabeledAbruptCompletion(_, l) } + } + + signature module InputSig2 { + /** Holds if this catch clause catches all exceptions. */ + default predicate catchAll(CatchClause catch) { none() } + + /** + * Holds if this case matches all possible values, for example, if it is a + * `default` case or a match-all pattern like `Object o` or if it is the + * final case in a switch that is known to be exhaustive. + * + * A match-all case can still ultimately fail to match if it has a guard. + */ + default predicate matchAll(Case c) { none() } + + /** + * Holds if `ast` may result in an abrupt completion `c` originating at + * `n`. The boolean `always` indicates whether the abrupt completion + * always occurs or whether `n` may also terminate normally. + */ + predicate beginAbruptCompletion( + AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always + ); + + /** + * Holds if an abrupt completion `c` from within `ast` is caught with + * flow continuing at `n`. + */ + predicate endAbruptCompletion(AstNode ast, PreControlFlowNode n, AbruptCompletion c); + + /** Holds if there is a local non-abrupt step from `n1` to `n2`. */ + predicate step(PreControlFlowNode n1, PreControlFlowNode n2); + } + + module Make2 { + /** + * Holds if `ast` may result in an abrupt completion `c` originating at + * `n`. The boolean `always` indicates whether the abrupt completion + * always occurs or whether `n` may also terminate normally. + */ + private predicate beginAbruptCompletion( + AstNode ast, PreControlFlowNode n, AbruptCompletion c, boolean always + ) { + Input2::beginAbruptCompletion(ast, n, c, always) + or + n.isIn(ast) and + always = true and + ( + ast instanceof ReturnStmt and + c.getSuccessorType() instanceof ReturnSuccessor + or + ast instanceof ThrowStmt and + c.getSuccessorType() instanceof ExceptionSuccessor + or + ast instanceof BreakStmt and + c.getSuccessorType() instanceof BreakSuccessor + or + ast instanceof ContinueStmt and + c.getSuccessorType() instanceof ContinueSuccessor + ) and + ( + not Input1::hasLabel(ast, _) and not c.hasLabel(_) + or + exists(Input1::Label l | + Input1::hasLabel(ast, l) and + c.hasLabel(l) + ) + ) + or + exists(TryStmt trystmt, int i, CatchClause catchclause | + trystmt.getCatch(i) = catchclause and + not exists(trystmt.getCatch(i + 1)) and + ast = catchclause and + n.isAfterValue(catchclause, any(MatchingSuccessor t | t.getValue() = false)) and + c.getSuccessorType() instanceof ExceptionSuccessor and + always = true + ) + } + + /** + * Holds if an abrupt completion `c` from within `ast` is caught with + * flow continuing at `n`. + */ + private predicate endAbruptCompletion(AstNode ast, PreControlFlowNode n, AbruptCompletion c) { + Input2::endAbruptCompletion(ast, n, c) + or + exists(Callable callable | ast = callableGetBody(callable) | + c.getSuccessorType() instanceof ReturnSuccessor and + n.(NormalExitNodeImpl).getEnclosingCallable() = callable + or + c.getSuccessorType() instanceof ExceptionSuccessor and + n.(ExceptionalExitNodeImpl).getEnclosingCallable() = callable + or + c.getSuccessorType() instanceof ExitSuccessor and + n.(ExceptionalExitNodeImpl).getEnclosingCallable() = callable + ) + or + exists(LoopStmt loop | ast = loop.getBody() | + ( + c.getSuccessorType() instanceof BreakSuccessor and + n.isAfter(loop) + or + c.getSuccessorType() instanceof ContinueSuccessor and + n.isAdditional(loop, loopHeaderTag()) + ) and + ( + not c.hasLabel(_) + or + exists(Input1::Label l | + c.hasLabel(l) and + Input1::hasLabel(loop, l) + ) + ) + ) + or + exists(TryStmt trystmt | + ast = getTryInit(trystmt, _) + or + ast = trystmt.getBody() + | + c.getSuccessorType() instanceof ExceptionSuccessor and + ( + n.isBefore(trystmt.getCatch(0)) + or + not exists(trystmt.getCatch(_)) and + n.isBefore(trystmt.getFinally()) + ) + or + ( + // Exit completions skip the finally block + c.getSuccessorType() instanceof ReturnSuccessor or + c.getSuccessorType() instanceof JumpSuccessor + ) and + n.isBefore(trystmt.getFinally()) + ) + or + exists(TryStmt trystmt | + ast = trystmt.getCatch(_) + or + ast = getTryElse(trystmt) + | + n.isBefore(trystmt.getFinally()) and + not c.getSuccessorType() instanceof ExitSuccessor + ) + or + exists(Switch switch | + ast = switch.getCase(_).getBodyElement(_) and + n.isAfter(switch) and + c.getSuccessorType() instanceof BreakSuccessor + | + not c.hasLabel(_) + or + exists(Input1::Label l | + c.hasLabel(l) and + Input1::hasLabel(switch, l) + ) + ) + } + + private Case getRankedCaseCfgOrder(Switch s, int rnk) { + result = rank[rnk](Case c, int i | getCaseControlFlowOrder(s, c) = i | c order by i) + } + + private AstNode getFirstCaseBodyElement(Case case) { + result = case.getBodyElement(0) + or + not exists(case.getBodyElement(0)) and + exists(Switch s, int i | + fallsThrough(case) and + // fall-through follows AST order, not case control flow order: + s.getCase(i) = case and + result = getFirstCaseBodyElement(s.getCase(i + 1)) + ) + } + + private AstNode getNextCaseBodyElement(AstNode bodyElement) { + exists(Case case, int i | case.getBodyElement(i) = bodyElement | + result = case.getBodyElement(i + 1) + or + not exists(case.getBodyElement(i + 1)) and + exists(Switch s, int j | + fallsThrough(case) and + // fall-through follows AST order, not case control flow order: + s.getCase(j) = case and + result = getFirstCaseBodyElement(s.getCase(j + 1)) + ) + ) + } + + /** Holds if there is a local non-abrupt step from `n1` to `n2`. */ + private predicate explicitStep(PreControlFlowNode n1, PreControlFlowNode n2) { + Input2::step(n1, n2) + or + exists(Callable c | + n1.(EntryNodeImpl).getEnclosingCallable() = c and + n2.isBefore(callableGetBody(c)) + or + n1.isAfter(callableGetBody(c)) and + n2.(NormalExitNodeImpl).getEnclosingCallable() = c + or + n1.(AnnotatedExitNodeImpl).getEnclosingCallable() = + n2.(ExitNodeImpl).getEnclosingCallable() + ) + or + exists(AstNode child, AstNode parent | propagatesValue(child, parent) | + exists(ConditionalSuccessor t | + inConditionalContext(parent, t.getKind()) and + n1.isAfterValue(child, t) and + n2.isAfterValue(parent, t) + ) + or + not inConditionalContext(parent, _) and + n1.isAfter(child) and + n2.isAfter(parent) + ) + or + exists(BinaryExpr binexpr, ConditionalSuccessor shortcircuitValue | + shortCircuiting(binexpr, shortcircuitValue) + | + n1.isBefore(binexpr) and + n2.isBefore(binexpr.getLeftOperand()) + or + n1.isAfterValue(binexpr.getLeftOperand(), shortcircuitValue.getDual()) and + n2.isBefore(binexpr.getRightOperand()) + or + n1.isAfterValue(binexpr.getLeftOperand(), shortcircuitValue) and + n2.isAfterValue(binexpr, shortcircuitValue) + or + // short-circuiting operations with side-effects (e.g. `x &&= y`, `x?.Prop = y`) are in post-order: + n1.isAfter(binexpr.getRightOperand()) and + n2.isIn(binexpr) + or + n1.isIn(binexpr) and + n2.isAfter(binexpr) + ) + or + exists(LogicalNotExpr notexpr | + n1.isBefore(notexpr) and + n2.isBefore(notexpr.getOperand()) + or + exists(BooleanSuccessor t | + n1.isAfterValue(notexpr.getOperand(), t) and + n2.isAfterValue(notexpr, t.getDual()) + ) + ) + or + exists(ConditionalExpr condexpr | + n1.isBefore(condexpr) and + n2.isBefore(condexpr.getCondition()) + or + n1.isAfterTrue(condexpr.getCondition()) and + n2.isBefore(condexpr.getThen()) + or + n1.isAfterFalse(condexpr.getCondition()) and + n2.isBefore(condexpr.getElse()) + ) + or + exists(BooleanLiteral boollit | + inConditionalContext(boollit, _) and + n1.isBefore(boollit) and + n2.isAfterValue(boollit, any(BooleanSuccessor t | t.getValue() = boollit.getValue())) + ) + or + exists(ExprStmt exprstmt | + n1.isBefore(exprstmt) and + n2.isBefore(exprstmt.getExpr()) + // the `isAfter(exprstmt.getExpr())` to `isAfter(exprstmt)` case is handled by `propagatesValue` above. + ) + or + exists(BlockStmt blockstmt | + n1.isBefore(blockstmt) and + n2.isBefore(blockstmt.getStmt(0)) + or + not exists(blockstmt.getStmt(_)) and + n1.isBefore(blockstmt) and + n2.isAfter(blockstmt) and + not simpleLeafNode(blockstmt) + or + exists(int i | + n1.isAfter(blockstmt.getStmt(i)) and + n2.isBefore(blockstmt.getStmt(i + 1)) + ) + // the `isAfter(blockstmt.getLastStmt())` to `isAfter(blockstmt)` case is handled by `propagatesValue` above. + ) + or + exists(IfStmt ifstmt | + n1.isBefore(ifstmt) and + n2.isBefore(ifstmt.getCondition()) + or + n1.isAfterTrue(ifstmt.getCondition()) and + n2.isBefore(ifstmt.getThen()) + or + n1.isAfterFalse(ifstmt.getCondition()) and + ( + n2.isBefore(ifstmt.getElse()) + or + not exists(ifstmt.getElse()) and + n2.isAfter(ifstmt) + ) + or + n1.isAfter(ifstmt.getThen()) and + n2.isAfter(ifstmt) + or + n1.isAfter(ifstmt.getElse()) and + n2.isAfter(ifstmt) + ) + or + exists(WhileStmt whilestmt | + n1.isBefore(whilestmt) and + n2.isAdditional(whilestmt, loopHeaderTag()) + ) + or + exists(DoStmt dostmt | + n1.isBefore(dostmt) and + n2.isBefore(dostmt.getBody()) + ) + or + exists(LoopStmt loopstmt, AstNode cond | + loopstmt.(WhileStmt).getCondition() = cond or loopstmt.(DoStmt).getCondition() = cond + | + n1.isAdditional(loopstmt, loopHeaderTag()) and + n2.isBefore(cond) + or + n1.isAfterTrue(cond) and + n2.isBefore(loopstmt.getBody()) + or + n1.isAfterFalse(cond) and + n2.isAfter(loopstmt) + or + n1.isAfter(loopstmt.getBody()) and + n2.isAdditional(loopstmt, loopHeaderTag()) + ) + or + exists(ForeachStmt foreachstmt | + n1.isBefore(foreachstmt) and + n2.isBefore(foreachstmt.getCollection()) + or + n1.isAfterValue(foreachstmt.getCollection(), + any(EmptinessSuccessor t | t.getValue() = true)) and + n2.isAfter(foreachstmt) + or + n1.isAfterValue(foreachstmt.getCollection(), + any(EmptinessSuccessor t | t.getValue() = false)) and + n2.isBefore(foreachstmt.getVariable()) + or + n1.isAfter(foreachstmt.getVariable()) and + n2.isBefore(foreachstmt.getBody()) + or + n1.isAfter(foreachstmt.getBody()) and + n2.isAdditional(foreachstmt, loopHeaderTag()) + or + n1.isAdditional(foreachstmt, loopHeaderTag()) and + n2.isAfter(foreachstmt) + or + n1.isAdditional(foreachstmt, loopHeaderTag()) and + n2.isBefore(foreachstmt.getVariable()) + ) + or + exists(ForStmt forstmt, PreControlFlowNode condentry | + // Any part of the control flow that aims for the condition needs to hit either the condition... + condentry.isBefore(forstmt.getCondition()) + or + // ...or the body if the for doesn't include a condition. + not exists(forstmt.getCondition()) and condentry.isBefore(forstmt.getBody()) + | + n1.isBefore(forstmt) and + ( + n2.isBefore(forstmt.getInit(0)) + or + not exists(forstmt.getInit(_)) and n2 = condentry + ) + or + exists(int i | n1.isAfter(forstmt.getInit(i)) | + n2.isBefore(forstmt.getInit(i + 1)) + or + not exists(forstmt.getInit(i + 1)) and n2 = condentry + ) + or + n1.isAfterTrue(forstmt.getCondition()) and + n2.isBefore(forstmt.getBody()) + or + n1.isAfterFalse(forstmt.getCondition()) and + n2.isAfter(forstmt) + or + n1.isAfter(forstmt.getBody()) and + n2.isAdditional(forstmt, loopHeaderTag()) + or + n1.isAdditional(forstmt, loopHeaderTag()) and + ( + n2.isBefore(forstmt.getUpdate(0)) + or + not exists(forstmt.getUpdate(_)) and n2 = condentry + ) + or + exists(int i | n1.isAfter(forstmt.getUpdate(i)) | + n2.isBefore(forstmt.getUpdate(i + 1)) + or + not exists(forstmt.getUpdate(i + 1)) and n2 = condentry + ) + ) + or + exists(TryStmt trystmt | + n1.isBefore(trystmt) and + ( + n2.isBefore(getTryInit(trystmt, 0)) + or + not exists(getTryInit(trystmt, _)) and n2.isBefore(trystmt.getBody()) + ) + or + exists(int i | n1.isAfter(getTryInit(trystmt, i)) | + n2.isBefore(getTryInit(trystmt, i + 1)) + or + not exists(getTryInit(trystmt, i + 1)) and n2.isBefore(trystmt.getBody()) + ) + or + exists(PreControlFlowNode beforeElse, PreControlFlowNode beforeFinally | + ( + beforeElse.isBefore(getTryElse(trystmt)) + or + not exists(getTryElse(trystmt)) and beforeElse = beforeFinally + ) and + ( + beforeFinally.isBefore(trystmt.getFinally()) + or + not exists(trystmt.getFinally()) and beforeFinally.isAfter(trystmt) + ) + | + n1.isAfter(trystmt.getBody()) and + n2 = beforeElse + or + n1.isAfter(getTryElse(trystmt)) and + n2 = beforeFinally + or + n1.isAfter(trystmt.getCatch(_).getBody()) and + n2 = beforeFinally + ) + or + n1.isAfter(trystmt.getFinally()) and + n2.isAfter(trystmt) + or + exists(int i | + n1.isAfterValue(trystmt.getCatch(i), any(MatchingSuccessor t | t.getValue() = false)) and + n2.isBefore(trystmt.getCatch(i + 1)) + ) + ) + or + exists(CatchClause catchclause | + exists(MatchingSuccessor t | + n1.isBefore(catchclause) and + n2.isAfterValue(catchclause, t) and + if Input2::catchAll(catchclause) then t.getValue() = true else any() + ) + or + exists(PreControlFlowNode beforeVar, PreControlFlowNode beforeCond | + ( + beforeVar.isBefore(catchclause.getVariable()) + or + not exists(catchclause.getVariable()) and beforeVar = beforeCond + ) and + ( + beforeCond.isBefore(catchclause.getCondition()) + or + not exists(catchclause.getCondition()) and beforeCond.isBefore(catchclause.getBody()) + ) + | + n1.isAfterValue(catchclause, any(MatchingSuccessor t | t.getValue() = true)) and + n2 = beforeVar + or + n1.isAfter(catchclause.getVariable()) and + n2 = beforeCond + ) + or + n1.isAfterTrue(catchclause.getCondition()) and + n2.isBefore(catchclause.getBody()) + or + n1.isAfterFalse(catchclause.getCondition()) and + n2.isAfterValue(catchclause, any(MatchingSuccessor t | t.getValue() = false)) + ) + or + exists(Switch switch | + n1.isBefore(switch) and + n2.isBefore(switch.getExpr()) + or + n1.isBefore(switch) and + not exists(switch.getExpr()) and + n2.isBefore(getRankedCaseCfgOrder(switch, 1)) + or + n1.isAfter(switch.getExpr()) and + n2.isBefore(getRankedCaseCfgOrder(switch, 1)) + or + exists(int i | + n1.isAfterValue(getRankedCaseCfgOrder(switch, i), + any(MatchingSuccessor t | t.getValue() = false)) + | + n2.isBefore(getRankedCaseCfgOrder(switch, i + 1)) + or + not exists(getRankedCaseCfgOrder(switch, i + 1)) and + n2.isAfter(switch) + ) + ) + or + exists(Case case | + exists(MatchingSuccessor t | + n1.isBefore(case) and + n2.isAfterValue(case, t) and + if Input2::matchAll(case) then t.getValue() = true else any() + ) + or + exists( + PreControlFlowNode beforePattern, PreControlFlowNode beforeGuard, + PreControlFlowNode beforeBody + | + ( + beforePattern.isBefore(case.getAPattern()) + or + not exists(case.getAPattern()) and beforePattern = beforeGuard + ) and + ( + beforeGuard.isBefore(case.getGuard()) + or + not exists(case.getGuard()) and beforeGuard = beforeBody + ) and + ( + beforeBody.isBefore(getFirstCaseBodyElement(case)) + or + not exists(getFirstCaseBodyElement(case)) and + beforeBody.isAfter(any(Switch s | s.getCase(_) = case)) + ) + | + n1.isAfterValue(case, any(MatchingSuccessor t | t.getValue() = true)) and + n2 = beforePattern + or + n1.isAfter(case.getAPattern()) and + n2 = beforeGuard + or + n1.isAfterTrue(case.getGuard()) and + n2 = beforeBody + or + n1.isAfterFalse(case.getGuard()) and + n2.isAfterValue(case, any(MatchingSuccessor t | t.getValue() = false)) + ) + ) + or + exists(AstNode caseBodyElement | + n1.isAfter(caseBodyElement) and + n2.isBefore(getNextCaseBodyElement(caseBodyElement)) + or + n1.isAfter(caseBodyElement) and + not exists(getNextCaseBodyElement(caseBodyElement)) and + n2.isAfter(any(Switch s | s.getCase(_).getBodyElement(_) = caseBodyElement)) + ) + } + + /** + * Holds if `ast` does not have explicitly defined control flow steps + * and therefore should use default left-to-right evaluation. + */ + private predicate defaultCfg(AstNode ast) { + not explicitStep(any(PreControlFlowNode n | n.isBefore(ast)), _) + } + + private AstNode getRankedChild(AstNode parent, int rnk) { + defaultCfg(parent) and + result = rank[rnk](AstNode c, int ix | c = getChild(parent, ix) | c order by ix) + } + + /** + * Holds if `n1` to `n2` is a default left-to-right evaluation step for + * an `AstNode` that does not otherwise have explicitly defined control + * flow. + */ + private predicate defaultStep(PreControlFlowNode n1, PreControlFlowNode n2) { + exists(AstNode ast | defaultCfg(ast) | + n1.isBefore(ast) and + n2.isBefore(getRankedChild(ast, 1)) + or + exists(int i | + n1.isAfter(getRankedChild(ast, i)) and + n2.isBefore(getRankedChild(ast, i + 1)) + ) + or + ( + n1.isBefore(ast) and not exists(getRankedChild(ast, _)) and not simpleLeafNode(ast) + or + exists(int i | + n1.isAfter(getRankedChild(ast, i)) and not exists(getRankedChild(ast, i + 1)) + ) + ) and + (if postOrInOrder(ast) then n2.isIn(ast) else n2.isAfter(ast)) + or + n1.isIn(ast) and + n2.isAfter(ast) and + not beginAbruptCompletion(ast, n1, _, true) + ) + } + + /** Holds if there is a local non-abrupt step from `n1` to `n2`. */ + private predicate step(PreControlFlowNode n1, PreControlFlowNode n2) { + explicitStep(n1, n2) or defaultStep(n1, n2) + } + + /** + * Holds if the execution of `ast` may result in an abrupt completion + * `c` originating at `last`. + */ + private predicate last(AstNode ast, PreControlFlowNode last, AbruptCompletion c) { + // Require a predecessor as a coarse approximation of reachability. + // In particular, this prevents a catch-all catch clause preceding a + // finally block from adding exception edges out of the finally. + step(_, last) and + beginAbruptCompletion(ast, last, c, _) + or + exists(AstNode child | + getChild(ast, _) = child and + last(child, last, c) and + not endAbruptCompletion(child, _, c) + ) + or + exists( + AstNode inner, TryStmt try, Stmt finally, PreControlFlowNode finallyEntry, + PreControlFlowNode finallyExit + | + try.getFinally() = finally and + ast = finally and + finallyEntry.isBefore(finally) and + finallyExit.isAfter(finally) and + endAbruptCompletion(inner, finallyEntry, c) and + last(inner, _, c) and + last = finallyExit + ) + } + + private predicate succ(PreControlFlowNode n1, PreControlFlowNode n2, SuccessorType t) { + step(n1, n2) and n2 = TAfterValueNode(_, t) + or + step(n1, n2) and n2.(AdditionalNode).getSuccessorType() = t + or + step(n1, n2) and + not n2 instanceof AfterValueNode and + not n2 instanceof AdditionalNode and + t instanceof DirectSuccessor + or + exists(AstNode ast, AbruptCompletion c | + last(ast, n1, c) and endAbruptCompletion(ast, n2, c) and t = c.getSuccessorType() + ) + } + + /** Holds if `n` is reachable from an entry node. */ + private predicate reachable(PreControlFlowNode n) { + n instanceof EntryNodeImpl + or + exists(PreControlFlowNode mid | reachable(mid) and succ(mid, n, _)) + } + + /** + * A node in the control flow graph. This is restricted to nodes that + * are reachable from an entry node. + */ + final class ControlFlowNode extends PreControlFlowNode { + ControlFlowNode() { reachable(this) } + + /** Gets the basic block containing this control flow node. */ + BasicBlock getBasicBlock() { result.getANode() = this } + + /** Gets an immediate successor of a given type, if any. */ + ControlFlowNode getASuccessor(SuccessorType t) { succ(this, result, t) } + + /** Gets an immediate successor of this node. */ + ControlFlowNode getASuccessor() { result = this.getASuccessor(_) } + + /** Gets an immediate predecessor of this node. */ + ControlFlowNode getAPredecessor() { result.getASuccessor() = this } + + /** + * Gets a normal successor of this node, if any. This includes direct + * successors and conditional successors. + */ + ControlFlowNode getANormalSuccessor() { + result = this.getASuccessor(any(NormalSuccessor t)) + } + + /** Gets an exception successor of this node, if any. */ + ControlFlowNode getAnExceptionSuccessor() { + result = this.getASuccessor(any(ExceptionSuccessor t)) + } + } + + /** The control flow node at the entry point of a callable. */ + final class EntryNode extends ControlFlowNode, EntryNodeImpl { } + + /** A control flow node indicating the normal or exceptional termination of a callable. */ + final class AnnotatedExitNode extends ControlFlowNode, AnnotatedExitNodeImpl { } + + /** A control flow node indicating normal termination of a callable. */ + final class NormalExitNode extends AnnotatedExitNode, NormalExitNodeImpl { } + + /** A control flow node indicating exceptional termination of a callable. */ + final class ExceptionalExitNode extends AnnotatedExitNode, ExceptionalExitNodeImpl { } + + /** A control flow node indicating the termination of a callable. */ + final class ExitNode extends ControlFlowNode, ExitNodeImpl { } + + private import codeql.controlflow.BasicBlock as BB + + private module BbInput implements BB::InputSig { + predicate successorTypeIsCondition(SuccessorType t) { none() } + + class CfgScope = Ast::Callable; + + class Node = ControlFlowNode; + + CfgScope nodeGetCfgScope(Node node) { node.getEnclosingCallable() = result } + + Node nodeGetASuccessor(Node node, SuccessorType t) { result = node.getASuccessor(t) } + + predicate nodeIsDominanceEntry(Node node) { node instanceof EntryNode } + + predicate nodeIsPostDominanceExit(Node node) { node instanceof NormalExitNode } + } + + import CfgAlias + + private module CfgAlias = Cfg; + + module Cfg = BB::Make; + + /* + * CFG printing + */ + + private import PrintGraph as Pp + + private class ControlFlowNodeAlias = ControlFlowNode; + + private module PrintGraphInput implements Pp::InputSig { + class Callable = Ast::Callable; + + class ControlFlowNode = ControlFlowNodeAlias; + + ControlFlowNode getASuccessor(ControlFlowNode n, SuccessorType t) { + result = n.getASuccessor(t) + } + } + + import Pp::PrintGraph + + /* + * Consistency checks + * + * - there should be no dead ends except at ExitNodes + * - inConditionalContext(n, kind) kind must be unique for n + * - flow must preserve getEnclosingCallable + * - additionalNode(AstNode n, string tag, NormalSuccessor t) should have a unique t for (n, tag) + * - if "before" is reachable and node is post-or-in-order, then "in" must generally be reachable + */ + + /** Provides a set of consistency queries. */ + module Consistency { + /** Holds if `node` is lacking a successor. */ + query predicate deadEnd(ControlFlowNode node) { + not node instanceof ExitNode and + not exists(node.getASuccessor(_)) + } + + query predicate nonUniqueInConditionalContext(AstNode n) { + 1 < strictcount(ConditionKind kind | inConditionalContext(n, kind)) + } + + query predicate nonLocalStep(ControlFlowNode n1, SuccessorType t, ControlFlowNode n2) { + n1.getASuccessor(t) = n2 and + n1.getEnclosingCallable() != n2.getEnclosingCallable() + } + + query predicate ambiguousAdditionalNode(AstNode n, string tag) { + 1 < strictcount(NormalSuccessor t | additionalNode(n, tag, t)) + } + + query predicate missingInNodeForPostOrInOrder(AstNode ast) { + postOrInOrder(ast) and + exists(ControlFlowNode before | before.isBefore(ast)) and + not exists(ControlFlowNode mid | mid.isIn(ast)) and + // A non-terminating child could prevent reaching the "in" node, and that's fine: + not exists(AstNode child | + getChild(ast, _) = child and + exists(ControlFlowNode beforeChild | beforeChild.isBefore(child)) and + not exists(ControlFlowNode afterChild | afterChild.isAfter(child)) + ) + } + + /** Holds if `node` has multiple successors of the same type `t`. */ + query predicate multipleSuccessors( + ControlFlowNode node, SuccessorType t, ControlFlowNode successor + ) { + strictcount(node.getASuccessor(t)) > 1 and + successor = node.getASuccessor(t) and + // allow for loop headers in foreach loops (they're checking emptiness on the iterator, not the collection) + not ( + t instanceof DirectSuccessor and + node.isAdditional(any(ForeachStmt foreach), loopHeaderTag()) + ) and + // allow for functions with multiple bodies + not (t instanceof DirectSuccessor and node instanceof EntryNode) + } + + /** Holds if `node` has conditional successors of different kinds. */ + query predicate multipleConditionalSuccessorKinds( + ControlFlowNode node, ConditionalSuccessor t1, ConditionalSuccessor t2, + ControlFlowNode succ1, ControlFlowNode succ2 + ) { + t1.getKind() != t2.getKind() and + succ1 = node.getASuccessor(t1) and + succ2 = node.getASuccessor(t2) + } + + /** Holds if `node` has both a direct and a conditional successor type. */ + query predicate directAndConditionalSuccessors( + ControlFlowNode node, ConditionalSuccessor t1, DirectSuccessor t2, ControlFlowNode succ1, + ControlFlowNode succ2 + ) { + succ1 = node.getASuccessor(t1) and + succ2 = node.getASuccessor(t2) + } + } + } + } +}