Files
codeql/csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll
2026-03-05 09:11:13 +01:00

1806 lines
62 KiB
Plaintext

/**
* Provides an implementation for constructing control-flow graphs (CFGs) from
* abstract syntax trees (ASTs), using the shared library from `codeql.controlflow.Cfg`.
*/
import csharp
private import codeql.controlflow.Cfg as CfgShared
private import Completion
private import semmle.code.csharp.ExprOrStmtParent
private import semmle.code.csharp.commons.Compilation
private module Initializers {
/**
* A non-static member with an initializer, for example a field `int Field = 0`.
*/
class InitializedInstanceMember extends Member {
private AssignExpr ae;
InitializedInstanceMember() {
not this.isStatic() and
expr_parent_top_level(ae, _, this) and
not ae = any(Callable c).getExpressionBody()
}
/** Gets the initializer expression. */
AssignExpr getInitializer() { result = ae }
}
/**
* Holds if `obinit` is an object initializer method that performs the initialization
* of a member via assignment `init`.
*/
predicate obinitInitializes(ObjectInitMethod obinit, AssignExpr init) {
exists(InitializedInstanceMember m |
obinit.getDeclaringType().getAMember() = m and
init = m.getInitializer()
)
}
/**
* Gets the `i`th member initializer expression for object initializer method `obinit`
* in compilation `comp`.
*/
AssignExpr initializedInstanceMemberOrder(ObjectInitMethod obinit, CompilationExt comp, int i) {
obinitInitializes(obinit, result) and
result =
rank[i + 1](AssignExpr ae0, Location l |
obinitInitializes(obinit, ae0) and
l = ae0.getLocation() and
getCompilation(l.getFile()) = comp
|
ae0 order by l.getStartLine(), l.getStartColumn(), l.getFile().getAbsolutePath()
)
}
/**
* Gets the last member initializer expression for object initializer method `obinit`
* in compilation `comp`.
*/
AssignExpr lastInitializer(ObjectInitMethod obinit, CompilationExt comp) {
exists(int i |
result = initializedInstanceMemberOrder(obinit, comp, i) and
not exists(initializedInstanceMemberOrder(obinit, comp, i + 1))
)
}
}
/** An element that defines a new CFG scope. */
class CfgScope extends Element, @top_level_exprorstmt_parent {
CfgScope() {
this.getFile().fromSource() and
(
this =
any(Callable c |
c.(Constructor).hasInitializer()
or
Initializers::obinitInitializes(c, _)
or
c.hasBody()
)
or
// For now, static initializer values have their own scope. Eventually, they
// should be treated like instance initializers.
this.(Assignable).(Modifiable).isStatic() and
expr_parent_top_level_adjusted2(_, _, this)
)
}
}
private class TAstNode = @callable or @control_flow_element;
private Element getAChild(Element p) {
result = p.getAChild() or
result = p.(AssignOperation).getExpandedAssignment()
}
pragma[nomagic]
private predicate astNode(Element e) {
e = any(@top_level_exprorstmt_parent p | not p instanceof Attribute)
or
exists(Element parent |
astNode(parent) and
e = getAChild(parent)
)
}
/** An AST node. */
class AstNode extends Element, TAstNode {
AstNode() { astNode(this) }
int getId() { idOf(this, result) }
}
private predicate id(AstNode x, AstNode y) { x = y }
private predicate idOf(AstNode x, int y) = equivalenceRelation(id/2)(x, y)
private module CfgInput implements CfgShared::InputSig<Location> {
private import ControlFlowGraphImpl as Impl
private import Completion as Comp
private import SuccessorType as ST
private import semmle.code.csharp.Caching
class AstNode = Impl::AstNode;
class Completion = Comp::Completion;
predicate completionIsNormal(Completion c) { c instanceof Comp::NormalCompletion }
predicate completionIsSimple(Completion c) { c instanceof Comp::SimpleCompletion }
predicate completionIsValidFor(Completion c, AstNode e) { c.isValidFor(e) }
class CfgScope = Impl::CfgScope;
CfgScope getCfgScope(AstNode n) {
Stages::ControlFlowStage::forceCachingInSameStage() and
result = n.(ControlFlowElement).getEnclosingCallable()
}
predicate scopeFirst(CfgScope scope, AstNode first) { Impl::scopeFirst(scope, first) }
predicate scopeLast(CfgScope scope, AstNode last, Completion c) {
Impl::scopeLast(scope, last, c)
}
private class SuccessorType = ST::SuccessorType;
SuccessorType getAMatchingSuccessorType(Completion c) { result = c.getAMatchingSuccessorType() }
int idOfAstNode(AstNode node) { result = node.getId() }
int idOfCfgScope(CfgScope node) { result = idOfAstNode(node) }
}
private module CfgSplittingInput implements CfgShared::SplittingInputSig<Location, CfgInput> {
private import Splitting as S
class SplitKindBase = S::TSplitKind;
class Split = S::Split;
}
private module ConditionalCompletionSplittingInput implements
CfgShared::ConditionalCompletionSplittingInputSig<Location, CfgInput, CfgSplittingInput>
{
import Splitting::ConditionalCompletionSplitting::ConditionalCompletionSplittingInput
}
import CfgShared::MakeWithSplitting<Location, CfgInput, CfgSplittingInput, ConditionalCompletionSplittingInput>
/**
* A compilation.
*
* Unlike the standard `Compilation` class, this class also supports buildless
* extraction.
*/
newtype CompilationExt =
TCompilation(Compilation c) { not extractionIsStandalone() } or
TBuildless() { extractionIsStandalone() }
/** Gets the compilation that source file `f` belongs to. */
CompilationExt getCompilation(File f) {
exists(Compilation c |
f = c.getAFileCompiled() and
result = TCompilation(c)
)
or
result = TBuildless()
}
/**
* The `expr_parent_top_level_adjusted()` relation restricted to exclude relations
* between properties and their getters' expression bodies in properties such as
* `int P => 0`.
*
* This is in order to only associate the expression body with one CFG scope, namely
* the getter (and not the declaration itself).
*/
private predicate expr_parent_top_level_adjusted2(
Expr child, int i, @top_level_exprorstmt_parent parent
) {
expr_parent_top_level_adjusted(child, i, parent) and
not exists(Getter g |
g.getDeclaration() = parent and
i = 0
)
}
/** Holds if `first` is first executed when entering `scope`. */
predicate scopeFirst(CfgScope scope, AstNode first) {
scope =
any(Callable c |
if exists(c.(Constructor).getObjectInitializerCall())
then first(c.(Constructor).getObjectInitializerCall(), first)
else
if exists(c.(Constructor).getInitializer())
then first(c.(Constructor).getInitializer(), first)
else first(c.getBody(), first)
)
or
first(Initializers::initializedInstanceMemberOrder(scope, _, 0), first)
or
expr_parent_top_level_adjusted2(any(Expr e | first(e, first)), _, scope) and
not scope instanceof Callable
}
/** Holds if `scope` is exited when `last` finishes with completion `c`. */
predicate scopeLast(CfgScope scope, AstNode last, Completion c) {
scope =
any(Callable callable |
last(callable.getBody(), last, c) and
not c instanceof GotoCompletion
or
last(callable.(Constructor).getInitializer(), last, c) and
not callable.hasBody()
or
// This is only relevant in the context of compilation errors, since
// normally the existence of an object initializer call implies the
// existence of an initializer.
last(callable.(Constructor).getObjectInitializerCall(), last, c) and
not callable.(Constructor).hasInitializer() and
not callable.hasBody()
)
or
last(Initializers::lastInitializer(scope, _), last, c)
or
expr_parent_top_level_adjusted2(any(Expr e | last(e, last, c)), _, scope) and
not scope instanceof Callable
}
private class ObjectInitTree extends ControlFlowTree instanceof ObjectInitMethod {
final override predicate propagatesAbnormal(AstNode child) { none() }
final override predicate first(AstNode first) { none() }
final override predicate last(AstNode last, Completion c) { none() }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
exists(CompilationExt comp, int i |
// Flow from one member initializer to the next
last(Initializers::initializedInstanceMemberOrder(this, comp, i), pred, c) and
c instanceof NormalCompletion and
first(Initializers::initializedInstanceMemberOrder(this, comp, i + 1), succ)
)
}
}
private class ConstructorTree extends ControlFlowTree instanceof Constructor {
final override predicate propagatesAbnormal(AstNode child) { none() }
final override predicate first(AstNode first) { none() }
final override predicate last(AstNode last, Completion c) { none() }
/** Gets the body of this constructor belonging to compilation `comp`. */
pragma[noinline]
AstNode getBody(CompilationExt comp) {
result = super.getBody() and
comp = getCompilation(result.getFile())
}
pragma[noinline]
private MethodCall getObjectInitializerCall(CompilationExt comp) {
result = super.getObjectInitializerCall() and
comp = getCompilation(result.getFile())
}
pragma[noinline]
private ConstructorInitializer getInitializer(CompilationExt comp) {
result = super.getInitializer() and
comp = getCompilation(result.getFile())
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
exists(CompilationExt comp |
last(this.getObjectInitializerCall(comp), pred, c) and
c instanceof NormalCompletion
|
first(this.getInitializer(comp), succ)
or
// This is only relevant in the context of compilation errors, since
// normally the existence of an object initializer call implies the
// existence of an initializer.
not exists(this.getInitializer(comp)) and
first(this.getBody(comp), succ)
)
}
}
abstract private class SwitchTree extends ControlFlowTree instanceof Switch {
override predicate propagatesAbnormal(AstNode child) { child = super.getExpr() }
override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from last element of switch expression to first element of first case
last(super.getExpr(), pred, c) and
c instanceof NormalCompletion and
first(super.getCase(0), succ)
or
// Flow from last element of case pattern to next case
exists(Case case, int i | case = super.getCase(i) |
last(case.getPattern(), pred, c) and
c.(MatchingCompletion).isNonMatch() and
first(super.getCase(i + 1), succ)
)
or
// Flow from last element of condition to next case
exists(Case case, int i | case = super.getCase(i) |
last(case.getCondition(), pred, c) and
c instanceof FalseCompletion and
first(super.getCase(i + 1), succ)
)
}
}
abstract private class CaseTree extends ControlFlowTree instanceof Case {
final override predicate propagatesAbnormal(AstNode child) {
child in [super.getPattern().(ControlFlowElement), super.getCondition(), super.getBody()]
}
override predicate succ(AstNode pred, AstNode succ, Completion c) {
last(super.getPattern(), pred, c) and
c.(MatchingCompletion).isMatch() and
(
if exists(super.getCondition())
then
// Flow from the last element of pattern to the condition
first(super.getCondition(), succ)
else
// Flow from last element of pattern to first element of body
first(super.getBody(), succ)
)
or
// Flow from last element of condition to first element of body
last(super.getCondition(), pred, c) and
c instanceof TrueCompletion and
first(super.getBody(), succ)
}
}
module Expressions {
/** An expression that should not be included in the control flow graph. */
abstract private class NoNodeExpr extends Expr { }
private class SimpleNoNodeExpr extends NoNodeExpr {
SimpleNoNodeExpr() {
this instanceof TypeAccess and
not this instanceof TypeAccessPatternExpr
}
}
/** A write access that is not also a read access. */
private class WriteAccess extends AssignableWrite {
WriteAccess() {
// `x++` is both a read and write access
not this instanceof AssignableRead
}
}
private class WriteAccessNoNodeExpr extends WriteAccess, NoNodeExpr {
WriteAccessNoNodeExpr() {
// For example a write to a static field, `Foo.Bar = 0`.
forall(Expr e | e = this.getAChildExpr() | e instanceof NoNodeExpr)
}
}
private AstNode getExprChild0(Expr e, int i) {
not e instanceof NameOfExpr and
not e instanceof QualifiableExpr and
not e instanceof Assignment and
not e instanceof AnonymousFunctionExpr and
result = e.getChild(i)
or
e = any(ExtensionMethodCall emc | result = emc.getArgument(i))
or
e =
any(QualifiableExpr qe |
not qe instanceof ExtensionMethodCall and
result = qe.getChild(i)
)
or
e =
any(Assignment a |
// The left-hand side of an assignment is evaluated before the right-hand side
i = 0 and result = a.getLValue()
or
i = 1 and result = a.getRValue()
)
}
private AstNode getExprChild(Expr e, int i) {
result =
rank[i + 1](AstNode cfe, int j |
cfe = getExprChild0(e, j) and
not cfe instanceof NoNodeExpr
|
cfe order by j
)
}
private AstNode getLastExprChild(Expr e) {
exists(int last |
result = getExprChild(e, last) and
not exists(getExprChild(e, last + 1))
)
}
private class StandardExpr extends StandardPostOrderTree instanceof Expr {
StandardExpr() {
// The following expressions need special treatment
not this instanceof LogicalNotExpr and
not this instanceof LogicalAndExpr and
not this instanceof LogicalOrExpr and
not this instanceof NullCoalescingExpr and
not this instanceof ConditionalExpr and
not this instanceof AssignOperationWithExpandedAssignment and
not this instanceof ConditionallyQualifiedExpr and
not this instanceof ThrowExpr and
not this instanceof ObjectCreation and
not this instanceof ArrayCreation and
not this instanceof QualifiedWriteAccess and
not this instanceof QualifiedAccessorWrite and
not this instanceof NoNodeExpr and
not this instanceof SwitchExpr and
not this instanceof SwitchCaseExpr and
not this instanceof ConstructorInitializer and
not this instanceof NotPatternExpr and
not this instanceof OrPatternExpr and
not this instanceof AndPatternExpr and
not this instanceof RecursivePatternExpr and
not this instanceof PositionalPatternExpr and
not this instanceof PropertyPatternExpr
}
final override AstNode getChildNode(int i) { result = getExprChild(this, i) }
}
/**
* A qualified write access.
*
* The successor declaration in `QualifiedAccessorWrite` ensures that the access itself
* is evaluated after the qualifier and the indexer arguments (if any)
* and the right hand side of the assignment.
*
* When a qualified write access is used as an `out/ref` argument, the access itself is evaluated immediately.
*/
private class QualifiedWriteAccess extends ControlFlowTree instanceof WriteAccess, QualifiableExpr
{
QualifiedWriteAccess() {
(
this.hasQualifier()
or
// Member initializers like
// ```csharp
// new Dictionary<int, string>() { [0] = "Zero", [1] = "One", [2] = "Two" }
// ```
// need special treatment, because the accesses `[0]`, `[1]`, and `[2]`
// have no qualifier.
this = any(MemberInitializer mi).getLValue()
) and
not exists(AssignableDefinitions::OutRefDefinition def | def.getTargetAccess() = this)
}
final override predicate propagatesAbnormal(AstNode child) { child = getExprChild(this, _) }
final override predicate first(AstNode first) { first(getExprChild(this, 0), first) }
final override predicate last(AstNode last, Completion c) {
// Skip the access in a qualified write access
last(getLastExprChild(this), last, c)
or
// Qualifier exits with a null completion
super.isConditional() and
last(super.getQualifier(), last, c) and
c.(NullnessCompletion).isNull()
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
exists(int i |
last(getExprChild(this, i), pred, c) and
c instanceof NormalCompletion and
(if i = 0 then not c.(NullnessCompletion).isNull() else any()) and
first(getExprChild(this, i + 1), succ)
)
}
}
/**
* An expression that writes via a qualifiable expression, for example `x.Prop = 0`,
* where `Prop` is a property.
*
* Accessor writes need special attention, because we need to model the fact
* that the accessor is called *after* the assigned value has been evaluated.
* In the example above, this means we want a CFG that looks like
*
* ```csharp
* x -> 0 -> set_Prop -> x.Prop = 0
* ```
*
* For consistency, control flow is implemented the same way for other qualified writes.
* For example, `x.Field = 0`, where `Field` is a field, we want a CFG that looks like
*
* ```csharp
* x -> 0 -> x.Field -> x.Field = 0
* ```
*/
private class QualifiedAccessorWrite extends PostOrderTree instanceof Expr {
AssignableDefinition def;
QualifiedAccessorWrite() {
def.getExpr() = this and
def.getTargetAccess().(WriteAccess) instanceof QualifiableExpr and
not def instanceof AssignableDefinitions::OutRefDefinition and
not this instanceof AssignOperationWithExpandedAssignment
}
/**
* Gets the `i`th accessor being called in this write. More than one call
* can happen in tuple assignments.
*/
QualifiableExpr getAccess(int i) {
result =
rank[i + 1](AssignableDefinitions::TupleAssignmentDefinition tdef |
tdef.getExpr() = this and
tdef.getTargetAccess() instanceof QualifiableExpr
|
tdef order by tdef.getEvaluationOrder()
).getTargetAccess()
or
i = 0 and
result = def.getTargetAccess() and
not def instanceof AssignableDefinitions::TupleAssignmentDefinition
}
final override predicate propagatesAbnormal(AstNode child) {
child = getExprChild(this, _)
or
child = this.getAccess(_)
}
final override predicate last(AstNode last, Completion c) {
PostOrderTree.super.last(last, c)
or
last(getExprChild(this, 0), last, c) and c.(NullnessCompletion).isNull()
}
final override predicate first(AstNode first) { first(getExprChild(this, 0), first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Standard left-to-right evaluation
exists(int i |
last(getExprChild(this, i), pred, c) and
c instanceof NormalCompletion and
(if i = 0 then not c.(NullnessCompletion).isNull() else any()) and
first(getExprChild(this, i + 1), succ)
)
or
// Flow from last element of last child to first accessor call
last(getLastExprChild(this), pred, c) and
succ = this.getAccess(0) and
c instanceof NormalCompletion
or
// Flow from one call to the next
exists(int i | pred = this.getAccess(i) |
succ = this.getAccess(i + 1) and
c.isValidFor(pred) and
c instanceof NormalCompletion
)
or
// Post-order: flow from last call to element itself
exists(int last | last = max(int i | exists(this.getAccess(i))) |
pred = this.getAccess(last) and
succ = this and
c.isValidFor(pred) and
c instanceof NormalCompletion
)
}
}
private class LogicalNotExprTree extends PostOrderTree instanceof LogicalNotExpr {
private Expr operand;
LogicalNotExprTree() { operand = this.getOperand() }
final override predicate propagatesAbnormal(AstNode child) { child = operand }
final override predicate first(AstNode first) { first(operand, first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
succ = this and
last(operand, pred, c) and
c instanceof NormalCompletion
}
}
private class LogicalAndExprTree extends PostOrderTree instanceof LogicalAndExpr {
final override predicate propagatesAbnormal(AstNode child) {
child in [super.getLeftOperand(), super.getRightOperand()]
}
final override predicate first(AstNode first) { first(super.getLeftOperand(), first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from last element of left operand to first element of right operand
last(super.getLeftOperand(), pred, c) and
c instanceof TrueCompletion and
first(super.getRightOperand(), succ)
or
// Post-order: flow from last element of left operand to element itself
last(super.getLeftOperand(), pred, c) and
c instanceof FalseCompletion and
succ = this
or
// Post-order: flow from last element of right operand to element itself
last(super.getRightOperand(), pred, c) and
c instanceof NormalCompletion and
succ = this
}
}
private class LogicalOrExprTree extends PostOrderTree instanceof LogicalOrExpr {
final override predicate propagatesAbnormal(AstNode child) {
child in [super.getLeftOperand(), super.getRightOperand()]
}
final override predicate first(AstNode first) { first(super.getLeftOperand(), first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from last element of left operand to first element of right operand
last(super.getLeftOperand(), pred, c) and
c instanceof FalseCompletion and
first(super.getRightOperand(), succ)
or
// Post-order: flow from last element of left operand to element itself
last(super.getLeftOperand(), pred, c) and
c instanceof TrueCompletion and
succ = this
or
// Post-order: flow from last element of right operand to element itself
last(super.getRightOperand(), pred, c) and
c instanceof NormalCompletion and
succ = this
}
}
private class NullCoalescingExprTree extends PostOrderTree instanceof NullCoalescingExpr {
final override predicate propagatesAbnormal(AstNode child) {
child in [super.getLeftOperand(), super.getRightOperand()]
}
final override predicate first(AstNode first) { first(super.getLeftOperand(), first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from last element of left operand to first element of right operand
last(super.getLeftOperand(), pred, c) and
c.(NullnessCompletion).isNull() and
first(super.getRightOperand(), succ)
or
// Post-order: flow from last element of left operand to element itself
last(super.getLeftOperand(), pred, c) and
succ = this and
c instanceof NormalCompletion and
not c.(NullnessCompletion).isNull()
or
// Post-order: flow from last element of right operand to element itself
last(super.getRightOperand(), pred, c) and
c instanceof NormalCompletion and
succ = this
}
}
private class ConditionalExprTree extends PostOrderTree instanceof ConditionalExpr {
final override predicate propagatesAbnormal(AstNode child) {
child in [super.getCondition(), super.getThen(), super.getElse()]
}
final override predicate first(AstNode first) { first(super.getCondition(), first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from last element of condition to first element of then branch
last(super.getCondition(), pred, c) and
c instanceof TrueCompletion and
first(super.getThen(), succ)
or
// Flow from last element of condition to first element of else branch
last(super.getCondition(), pred, c) and
c instanceof FalseCompletion and
first(super.getElse(), succ)
or
// Post-order: flow from last element of a branch to element itself
last([super.getThen(), super.getElse()], pred, c) and
c instanceof NormalCompletion and
succ = this
}
}
/**
* An assignment operation that has an expanded version. We use the expanded
* version in the control flow graph in order to get better data flow / taint
* tracking.
*/
private class AssignOperationWithExpandedAssignment extends ControlFlowTree instanceof AssignOperation
{
private Expr expanded;
AssignOperationWithExpandedAssignment() { expanded = this.getExpandedAssignment() }
final override predicate first(AstNode first) { first(expanded, first) }
final override predicate last(AstNode last, Completion c) { last(expanded, last, c) }
final override predicate propagatesAbnormal(AstNode child) { none() }
final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
}
/** A conditionally qualified expression. */
private class ConditionallyQualifiedExpr extends PostOrderTree instanceof QualifiableExpr {
private Expr qualifier;
ConditionallyQualifiedExpr() {
this.isConditional() and qualifier = getExprChild(this, 0) and not this instanceof WriteAccess
}
final override predicate propagatesAbnormal(AstNode child) { child = qualifier }
final override predicate first(AstNode first) { first(qualifier, first) }
pragma[nomagic]
private predicate lastQualifier(AstNode last, Completion c) { last(qualifier, last, c) }
final override predicate last(AstNode last, Completion c) {
PostOrderTree.super.last(last, c)
or
// Qualifier exits with a `null` completion
this.lastQualifier(last, c) and
c.(NullnessCompletion).isNull()
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
exists(int i |
last(getExprChild(this, i), pred, c) and
c instanceof NormalCompletion and
if i = 0 then c.(NullnessCompletion).isNonNull() else any()
|
// Post-order: flow from last element of last child to element itself
i = max(int j | exists(getExprChild(this, j))) and
succ = this
or
// Standard left-to-right evaluation
first(getExprChild(this, i + 1), succ)
)
}
}
private class ThrowExprTree extends PostOrderTree instanceof ThrowExpr {
final override predicate propagatesAbnormal(AstNode child) { child = super.getExpr() }
final override predicate first(AstNode first) { first(super.getExpr(), first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
last(super.getExpr(), pred, c) and
c instanceof NormalCompletion and
succ = this
}
}
private class ObjectCreationTree extends ControlFlowTree instanceof ObjectCreation {
private Expr getObjectCreationArgument(int i) {
i >= 0 and
if super.hasInitializer()
then result = getExprChild(this, i + 1)
else result = getExprChild(this, i)
}
final override predicate propagatesAbnormal(AstNode child) {
child = this.getObjectCreationArgument(_)
}
final override predicate first(AstNode first) {
first(this.getObjectCreationArgument(0), first)
or
not exists(this.getObjectCreationArgument(0)) and
first = this
}
final override predicate last(AstNode last, Completion c) {
// Post-order: element itself (when no initializer)
last = this and
not super.hasInitializer() and
c.isValidFor(this)
or
// Last element of initializer
last(super.getInitializer(), last, c)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from last element of argument `i` to first element of argument `i+1`
exists(int i | last(this.getObjectCreationArgument(i), pred, c) |
first(this.getObjectCreationArgument(i + 1), succ) and
c instanceof NormalCompletion
)
or
// Flow from last element of last argument to self
exists(int last | last = max(int i | exists(this.getObjectCreationArgument(i))) |
last(this.getObjectCreationArgument(last), pred, c) and
succ = this and
c instanceof NormalCompletion
)
or
// Flow from self to first element of initializer
pred = this and
first(super.getInitializer(), succ) and
c instanceof SimpleCompletion
}
}
private class ArrayCreationTree extends ControlFlowTree instanceof ArrayCreation {
final override predicate propagatesAbnormal(AstNode child) {
child = super.getALengthArgument()
}
final override predicate first(AstNode first) {
// First element of first length argument
first(super.getLengthArgument(0), first)
or
// No length argument: element itself
not exists(super.getLengthArgument(0)) and
first = this
}
final override predicate last(AstNode last, Completion c) {
// Post-order: element itself (when no initializer)
last = this and
not super.hasInitializer() and
c.isValidFor(this)
or
// Last element of initializer
last(super.getInitializer(), last, c)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from self to first element of initializer
pred = this and
first(super.getInitializer(), succ) and
c instanceof SimpleCompletion
or
exists(int i |
last(super.getLengthArgument(i), pred, c) and
c instanceof SimpleCompletion
|
// Flow from last length argument to self
i = max(int j | exists(super.getLengthArgument(j))) and
succ = this
or
// Flow from one length argument to the next
first(super.getLengthArgument(i + 1), succ)
)
}
}
private class SwitchExprTree extends PostOrderTree, SwitchTree instanceof SwitchExpr {
final override predicate propagatesAbnormal(AstNode child) {
SwitchTree.super.propagatesAbnormal(child)
or
child = super.getACase()
}
final override predicate first(AstNode first) { first(super.getExpr(), first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
SwitchTree.super.succ(pred, succ, c)
or
last(super.getACase(), pred, c) and
succ = this and
c instanceof NormalCompletion
}
}
private class SwitchCaseExprTree extends PostOrderTree, CaseTree instanceof SwitchCaseExpr {
final override predicate first(AstNode first) { first(super.getPattern(), first) }
pragma[noinline]
private predicate lastNoMatch(AstNode last, ConditionalCompletion cc) {
last([super.getPattern(), super.getCondition()], last, cc) and
(cc.(MatchingCompletion).isNonMatch() or cc instanceof FalseCompletion)
}
final override predicate last(AstNode last, Completion c) {
PostOrderTree.super.last(last, c)
or
// Last case exists with a non-match
exists(SwitchExpr se, int i, ConditionalCompletion cc |
this = se.getCase(i) and
not super.matchesAll() and
not exists(se.getCase(i + 1)) and
this.lastNoMatch(last, cc) and
c =
any(NestedCompletion nc |
nc.getNestLevel() = 0 and
nc.getInnerCompletion() = cc and
nc.getOuterCompletion()
.(ThrowCompletion)
.getExceptionClass()
.hasFullyQualifiedName("System", "InvalidOperationException")
)
)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
CaseTree.super.succ(pred, succ, c)
or
last(super.getBody(), pred, c) and
succ = this and
c instanceof NormalCompletion
}
}
private class ConstructorInitializerTree extends PostOrderTree instanceof ConstructorInitializer {
private ControlFlowTree getChildNode(int i) { result = getExprChild(this, i) }
final override predicate propagatesAbnormal(AstNode child) { child = this.getChildNode(_) }
final override predicate first(AstNode first) {
first(this.getChildNode(0), first)
or
not exists(this.getChildNode(0)) and
first = this
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Post-order: flow from last element of last child to element itself
exists(int lst |
lst = max(int i | exists(this.getChildNode(i))) and
last(this.getChildNode(lst), pred, c) and
succ = this and
c instanceof NormalCompletion
)
or
// Standard left-to-right evaluation
exists(int i |
last(this.getChildNode(i), pred, c) and
c instanceof NormalCompletion and
first(this.getChildNode(i + 1), succ)
)
or
exists(ConstructorTree con, CompilationExt comp |
last(this, pred, c) and
con = super.getConstructor() and
comp = getCompilation(this.getFile()) and
c instanceof NormalCompletion and
first(con.getBody(comp), succ)
)
}
}
private class NotPatternExprTree extends PostOrderTree instanceof NotPatternExpr {
final override predicate propagatesAbnormal(AstNode child) { child = super.getPattern() }
final override predicate first(AstNode first) { first(super.getPattern(), first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
succ = this and
last(super.getPattern(), pred, c) and
c instanceof NormalCompletion
}
}
private class AndPatternExprTree extends PostOrderTree instanceof AndPatternExpr {
final override predicate propagatesAbnormal(AstNode child) { child = super.getAnOperand() }
final override predicate first(AstNode first) { first(super.getLeftOperand(), first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from last element of left operand to first element of right operand
last(super.getLeftOperand(), pred, c) and
c.(MatchingCompletion).getValue() = true and
first(super.getRightOperand(), succ)
or
// Post-order: flow from last element of left operand to element itself
last(super.getLeftOperand(), pred, c) and
c.(MatchingCompletion).getValue() = false and
succ = this
or
// Post-order: flow from last element of right operand to element itself
last(super.getRightOperand(), pred, c) and
c instanceof MatchingCompletion and
succ = this
}
}
private class OrPatternExprTree extends PostOrderTree instanceof OrPatternExpr {
final override predicate propagatesAbnormal(AstNode child) { child = super.getAnOperand() }
final override predicate first(AstNode first) { first(super.getLeftOperand(), first) }
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from last element of left operand to first element of right operand
last(super.getLeftOperand(), pred, c) and
c.(MatchingCompletion).getValue() = false and
first(super.getRightOperand(), succ)
or
// Post-order: flow from last element of left operand to element itself
last(super.getLeftOperand(), pred, c) and
c.(MatchingCompletion).getValue() = true and
succ = this
or
// Post-order: flow from last element of right operand to element itself
last(super.getRightOperand(), pred, c) and
c instanceof MatchingCompletion and
succ = this
}
}
}
private class RecursivePatternExprTree extends PostOrderTree instanceof RecursivePatternExpr {
private Expr getTypeExpr() {
result = super.getVariableDeclExpr()
or
not exists(super.getVariableDeclExpr()) and
result = super.getTypeAccess()
}
private PatternExpr getChildPattern() {
result = super.getPositionalPatterns()
or
result = super.getPropertyPatterns()
}
final override predicate propagatesAbnormal(AstNode child) { child = this.getChildPattern() }
final override predicate first(AstNode first) {
first(this.getTypeExpr(), first)
or
not exists(this.getTypeExpr()) and
first(this.getChildPattern(), first)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from type test to child pattern
last(this.getTypeExpr(), pred, c) and
first(this.getChildPattern(), succ) and
c.(MatchingCompletion).getValue() = true
or
// Flow from type test to self
last(this.getTypeExpr(), pred, c) and
succ = this and
c.(MatchingCompletion).getValue() = false
or
// Flow from child pattern to self
last(this.getChildPattern(), pred, c) and
succ = this and
c instanceof MatchingCompletion
}
}
private class PositionalPatternExprTree extends PreOrderTree instanceof PositionalPatternExpr {
final override predicate propagatesAbnormal(AstNode child) { child = super.getPattern(_) }
final override predicate last(AstNode last, Completion c) {
last = this and
c.(MatchingCompletion).getValue() = false
or
last(super.getPattern(_), last, c) and
c.(MatchingCompletion).getValue() = false
or
exists(int lst |
last(super.getPattern(lst), last, c) and
not exists(super.getPattern(lst + 1))
)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from self to first pattern
pred = this and
c.(MatchingCompletion).getValue() = true and
first(super.getPattern(0), succ)
or
// Flow from one pattern to the next
exists(int i |
last(super.getPattern(i), pred, c) and
c.(MatchingCompletion).getValue() = true and
first(super.getPattern(i + 1), succ)
)
}
}
private class PropertyPatternExprExprTree extends PostOrderTree instanceof PropertyPatternExpr {
final override predicate propagatesAbnormal(AstNode child) { child = super.getPattern(_) }
final override predicate first(AstNode first) {
first(super.getPattern(0), first)
or
not exists(super.getPattern(0)) and
first = this
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from one pattern to the next
exists(int i |
last(super.getPattern(i), pred, c) and
c.(MatchingCompletion).getValue() = true and
first(super.getPattern(i + 1), succ)
)
or
// Post-order: flow from last element of failing pattern to element itself
last(super.getPattern(_), pred, c) and
c.(MatchingCompletion).getValue() = false and
succ = this
or
// Post-order: flow from last element of last pattern to element itself
exists(int last |
last(super.getPattern(last), pred, c) and
not exists(super.getPattern(last + 1)) and
c instanceof MatchingCompletion and
succ = this
)
}
}
module Statements {
private class StandardStmt extends StandardPreOrderTree instanceof Stmt {
StandardStmt() {
// The following statements need special treatment
not this instanceof IfStmt and
not this instanceof SwitchStmt and
not this instanceof CaseStmt and
not this instanceof LoopStmt and
not this instanceof TryStmt and
not this instanceof SpecificCatchClause and
not this instanceof JumpStmt and
not this instanceof LabeledStmt
}
private ControlFlowTree getChildNode0(int i) {
not this instanceof GeneralCatchClause and
not this instanceof FixedStmt and
not this instanceof UsingBlockStmt and
result = this.getChild(i)
or
this = any(GeneralCatchClause gcc | i = 0 and result = gcc.getBlock())
or
this =
any(FixedStmt fs |
result = fs.getVariableDeclExpr(i)
or
result = fs.getBody() and
i = max(int j | exists(fs.getVariableDeclExpr(j))) + 1
)
or
this =
any(UsingBlockStmt us |
result = us.getExpr() and
i = 0
or
result = us.getVariableDeclExpr(i)
or
result = us.getBody() and
i = max([1, count(us.getVariableDeclExpr(_))])
)
}
final override AstNode getChildNode(int i) {
result = rank[i + 1](AstNode cfe, int j | cfe = this.getChildNode0(j) | cfe order by j)
}
}
private class IfStmtTree extends PreOrderTree instanceof IfStmt {
final override predicate propagatesAbnormal(AstNode child) { child = super.getCondition() }
final override predicate last(AstNode last, Completion c) {
// Condition exits with a false completion and there is no `else` branch
last(super.getCondition(), last, c) and
c instanceof FalseCompletion and
not exists(super.getElse())
or
// Then branch exits with any completion
last(super.getThen(), last, c)
or
// Else branch exits with any completion
last(super.getElse(), last, c)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Pre-order: flow from statement itself to first element of condition
pred = this and
first(super.getCondition(), succ) and
c instanceof SimpleCompletion
or
last(super.getCondition(), pred, c) and
(
// Flow from last element of condition to first element of then branch
c instanceof TrueCompletion and first(super.getThen(), succ)
or
// Flow from last element of condition to first element of else branch
c instanceof FalseCompletion and first(super.getElse(), succ)
)
}
}
private class SwitchStmtTree extends PreOrderTree, SwitchTree instanceof SwitchStmt {
final override predicate last(AstNode last, Completion c) {
// Switch expression exits normally and there are no cases
not exists(super.getACase()) and
last(super.getExpr(), last, c) and
c instanceof NormalCompletion
or
// A statement exits with a `break` completion
last(super.getStmt(_), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
or
// A statement exits abnormally
last(super.getStmt(_), last, c) and
not c instanceof BreakCompletion and
not c instanceof NormalCompletion and
not any(LabeledStmtTree t |
t.hasLabelInCallable(c.(GotoCompletion).getLabel(), super.getEnclosingCallable())
) instanceof CaseStmt
or
// Last case exits with a non-match
exists(CaseStmt cs, int last_ |
last_ = max(int i | exists(super.getCase(i))) and
cs = super.getCase(last_)
|
last(cs.getPattern(), last, c) and
not c.(MatchingCompletion).isMatch()
or
last(cs.getCondition(), last, c) and
c instanceof FalseCompletion
)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
SwitchTree.super.succ(pred, succ, c)
or
// Pre-order: flow from statement itself to first switch expression
pred = this and
first(super.getExpr(), succ) and
c instanceof SimpleCompletion
or
// Flow from last element of non-`case` statement `i` to first element of statement `i+1`
exists(int i | last(super.getStmt(i), pred, c) |
not super.getStmt(i) instanceof CaseStmt and
c instanceof NormalCompletion and
first(super.getStmt(i + 1), succ)
)
or
// Flow from last element of `case` statement `i` to first element of statement `i+1`
exists(int i, Stmt body |
body = super.getStmt(i).(CaseStmt).getBody() and
// in case of fall-through cases, make sure to not jump from their shared body back
// to one of the fall-through cases
not body = super.getStmt(i + 1).(CaseStmt).getBody() and
last(body, pred, c)
|
c instanceof NormalCompletion and
first(super.getStmt(i + 1), succ)
)
}
}
private class CaseStmtTree extends PreOrderTree, CaseTree instanceof CaseStmt {
final override predicate last(AstNode last, Completion c) {
// Condition exists with a `false` completion
last(super.getCondition(), last, c) and
c instanceof FalseCompletion
or
// Case pattern exits with a non-match
last(super.getPattern(), last, c) and
not c.(MatchingCompletion).isMatch()
or
// Case body exits with any completion
last(super.getBody(), last, c)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
CaseTree.super.succ(pred, succ, c)
or
pred = this and
first(super.getPattern(), succ) and
c instanceof SimpleCompletion
}
}
abstract private class LoopStmtTree extends PreOrderTree instanceof LoopStmt {
final override predicate propagatesAbnormal(AstNode child) { child = super.getCondition() }
final override predicate last(AstNode last, Completion c) {
// Condition exits with a false completion
last(super.getCondition(), last, c) and
c instanceof FalseCompletion
or
// Body exits with a break completion
last(super.getBody(), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
or
// Body exits with a completion that does not continue the loop
last(super.getBody(), last, c) and
not c instanceof BreakCompletion and
not c.continuesLoop()
}
override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from last element of condition to first element of loop body
last(super.getCondition(), pred, c) and
c instanceof TrueCompletion and
first(super.getBody(), succ)
or
// Flow from last element of loop body back to first element of condition
not this instanceof ForStmt and
last(super.getBody(), pred, c) and
c.continuesLoop() and
first(super.getCondition(), succ)
}
}
private class WhileStmtTree extends LoopStmtTree instanceof WhileStmt {
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
LoopStmtTree.super.succ(pred, succ, c)
or
pred = this and
first(super.getCondition(), succ) and
c instanceof SimpleCompletion
}
}
private class DoStmtTree extends LoopStmtTree instanceof DoStmt {
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
LoopStmtTree.super.succ(pred, succ, c)
or
pred = this and
first(super.getBody(), succ) and
c instanceof SimpleCompletion
}
}
private class ForStmtTree extends LoopStmtTree instanceof ForStmt {
/** Gets the condition if it exists, otherwise the body. */
private AstNode getConditionOrBody() {
result = super.getCondition()
or
not exists(super.getCondition()) and
result = super.getBody()
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
LoopStmtTree.super.succ(pred, succ, c)
or
// Pre-order: flow from statement itself to first element of first initializer/
// condition/loop body
exists(AstNode next |
pred = this and
first(next, succ) and
c instanceof SimpleCompletion
|
next = super.getInitializer(0)
or
not exists(super.getInitializer(0)) and
next = this.getConditionOrBody()
)
or
// Flow from last element of initializer `i` to first element of initializer `i+1`
exists(int i | last(super.getInitializer(i), pred, c) |
c instanceof NormalCompletion and
first(super.getInitializer(i + 1), succ)
)
or
// Flow from last element of last initializer to first element of condition/loop body
exists(int last | last = max(int i | exists(super.getInitializer(i))) |
last(super.getInitializer(last), pred, c) and
c instanceof NormalCompletion and
first(this.getConditionOrBody(), succ)
)
or
// Flow from last element of condition into first element of loop body
last(super.getCondition(), pred, c) and
c instanceof TrueCompletion and
first(super.getBody(), succ)
or
// Flow from last element of loop body to first element of update/condition/self
exists(AstNode next |
last(super.getBody(), pred, c) and
c.continuesLoop() and
first(next, succ) and
if exists(super.getUpdate(0))
then next = super.getUpdate(0)
else next = this.getConditionOrBody()
)
or
// Flow from last element of update to first element of next update/condition/loop body
exists(AstNode next, int i |
last(super.getUpdate(i), pred, c) and
c instanceof NormalCompletion and
first(next, succ) and
if exists(super.getUpdate(i + 1))
then next = super.getUpdate(i + 1)
else next = this.getConditionOrBody()
)
}
}
private class ForeachStmtTree extends ControlFlowTree instanceof ForeachStmt {
final override predicate propagatesAbnormal(AstNode child) { child = super.getIterableExpr() }
final override predicate first(AstNode first) {
// Unlike most other statements, `foreach` statements are not modeled in
// pre-order, because we use the `foreach` node itself to represent the
// emptiness test that determines whether to execute the loop body
first(super.getIterableExpr(), first)
}
final override predicate last(AstNode last, Completion c) {
// Emptiness test exits with no more elements
last = this and
c.(EmptinessCompletion).isEmpty()
or
// Body exits with a break completion
last(super.getBody(), last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
or
// Body exits abnormally
last(super.getBody(), last, c) and
not c instanceof NormalCompletion and
not c instanceof ContinueCompletion and
not c instanceof BreakCompletion
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from last element of iterator expression to emptiness test
last(super.getIterableExpr(), pred, c) and
c instanceof NormalCompletion and
succ = this
or
// Flow from emptiness test to first element of variable declaration/loop body
pred = this and
c = any(EmptinessCompletion ec | not ec.isEmpty()) and
(
first(super.getVariableDeclExpr(), succ)
or
first(super.getVariableDeclTuple(), succ)
or
not exists(super.getVariableDeclExpr()) and
not exists(super.getVariableDeclTuple()) and
first(super.getBody(), succ)
)
or
// Flow from last element of variable declaration to first element of loop body
(
last(super.getVariableDeclExpr(), pred, c) or
last(super.getVariableDeclTuple(), pred, c)
) and
c instanceof SimpleCompletion and
first(super.getBody(), succ)
or
// Flow from last element of loop body back to emptiness test
last(super.getBody(), pred, c) and
c.continuesLoop() and
succ = this
}
}
pragma[nomagic]
private AstNode lastLastCatchClause(CatchClause cc, Completion c) {
cc.isLast() and
last(cc, result, c)
}
pragma[nomagic]
private AstNode lastCatchClauseBlock(CatchClause cc, Completion c) {
last(cc.getBlock(), result, c)
}
/** Gets a child of `cfe` that is in CFG scope `scope`. */
pragma[noinline]
private ControlFlowElement getAChildInScope(AstNode cfe, Callable scope) {
result = getAChild(cfe) and
scope = result.getEnclosingCallable()
}
class TryStmtTree extends PreOrderTree instanceof TryStmt {
final override predicate propagatesAbnormal(AstNode child) { child = super.getFinally() }
/**
* Gets a descendant that belongs to the finally block of this try statement.
*/
AstNode getAFinallyDescendant() {
result = super.getFinally()
or
exists(ControlFlowElement mid |
mid = this.getAFinallyDescendant() and
result = getAChildInScope(mid, mid.getEnclosingCallable()) and
not exists(TryStmt nestedTry |
result = nestedTry.getFinally() and
nestedTry != this
)
)
}
/**
* Holds if `innerTry` has a finally block and is immediately nested inside the
* finally block of this try statement.
*/
private predicate nestedFinally(TryStmt innerTry) {
exists(AstNode innerFinally |
innerFinally = getAChildInScope(this.getAFinallyDescendant(), super.getEnclosingCallable()) and
innerFinally = innerTry.getFinally()
)
}
/**
* Gets the finally-nesting level of this try statement. That is, the number of
* finally blocks that this try statement is nested under.
*/
int nestLevel() { result = count(TryStmtTree outer | outer.nestedFinally+(this)) }
/** Holds if `last` is a last element of the block of this try statement. */
pragma[nomagic]
predicate lastBlock(AstNode last, Completion c) { last(super.getBlock(), last, c) }
/**
* Gets a last element from a `try` or `catch` block of this try statement
* that may finish with completion `c`, such that control may be transferred
* to the finally block (if it exists), but only if `finalizable = true`.
*/
pragma[nomagic]
AstNode getAFinallyPredecessor(Completion c, boolean finalizable) {
// Exit completions skip the finally block
(if c instanceof ExitCompletion then finalizable = false else finalizable = true) and
(
this.lastBlock(result, c) and
(
// Any non-throw completion from the `try` block will always continue directly
// to the finally block
not c instanceof ThrowCompletion
or
// Any completion from the `try` block will continue to the finally block
// when there are no catch clauses
not exists(super.getACatchClause())
)
or
// Last element from any of the `catch` clause blocks continues to the finally block
result = lastCatchClauseBlock(super.getACatchClause(), c)
or
// Last element of last `catch` clause continues to the finally block
result = lastLastCatchClause(super.getACatchClause(), c)
)
}
pragma[nomagic]
private predicate lastFinally0(AstNode last, Completion c) { last(super.getFinally(), last, c) }
pragma[nomagic]
private predicate lastFinally(
AstNode last, NormalCompletion finally, Completion outer, int nestLevel
) {
this.lastFinally0(last, finally) and
exists(
this.getAFinallyPredecessor(any(Completion c0 | outer = c0.getOuterCompletion()), true)
) and
nestLevel = this.nestLevel()
}
final override predicate last(AstNode last, Completion c) {
exists(boolean finalizable | last = this.getAFinallyPredecessor(c, finalizable) |
// If there is no finally block, last elements are from the body, from
// the blocks of one of the `catch` clauses, or from the last `catch` clause
not super.hasFinally()
or
finalizable = false
)
or
this.lastFinally(last, c, any(NormalCompletion nc), _)
or
// If the finally block completes normally, it inherits any non-normal
// completion that was current before the finally block was entered
exists(int nestLevel |
c =
any(NestedCompletion nc |
this.lastFinally(last, nc.getAnInnerCompatibleCompletion(), nc.getOuterCompletion(),
nestLevel) and
// unbind
nc.getNestLevel() >= nestLevel and
nc.getNestLevel() <= nestLevel
)
)
}
/**
* Gets an exception type that is thrown by `cfe` in the block of this `try`
* statement. Throw completion `c` matches the exception type.
*/
ExceptionClass getAThrownException(AstNode cfe, ThrowCompletion c) {
this.lastBlock(cfe, c) and
result = c.getExceptionClass()
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Pre-order: flow from statement itself to first element of body
pred = this and
first(super.getBlock(), succ) and
c instanceof SimpleCompletion
or
// Flow from last element of body to first `catch` clause
exists(this.getAThrownException(pred, c)) and
first(super.getCatchClause(0), succ)
or
exists(CatchClause cc, int i | cc = super.getCatchClause(i) |
// Flow from one `catch` clause to the next
pred = cc and
last(super.getCatchClause(i), cc, c) and
first(super.getCatchClause(i + 1), succ) and
c = any(MatchingCompletion mc | not mc.isMatch())
or
// Flow from last element of `catch` clause filter to next `catch` clause
last(super.getCatchClause(i), pred, c) and
last(cc.getFilterClause(), pred, _) and
first(super.getCatchClause(i + 1), succ) and
c instanceof FalseCompletion
)
or
// Flow into finally block
pred = this.getAFinallyPredecessor(c, true) and
first(super.getFinally(), succ)
}
}
private class SpecificCatchClauseTree extends PreOrderTree instanceof SpecificCatchClause {
final override predicate propagatesAbnormal(AstNode child) { child = super.getFilterClause() }
pragma[nomagic]
private predicate lastFilterClause(AstNode last, Completion c) {
last(super.getFilterClause(), last, c)
}
/**
* Holds if the `try` block that this catch clause belongs to may throw an
* exception of type `c`, where no `catch` clause is guaranteed to catch it.
* This catch clause is the last catch clause in the try statement that
* it belongs to.
*/
pragma[nomagic]
private predicate throwMayBeUncaught(ThrowCompletion c) {
exists(TryStmt ts |
ts = super.getTryStmt() and
ts.(TryStmtTree).lastBlock(_, c) and
not ts.getACatchClause() instanceof GeneralCatchClause and
forall(SpecificCatchClause scc | scc = ts.getACatchClause() |
scc.hasFilterClause()
or
not c.getExceptionClass().getABaseType*() = scc.getCaughtExceptionType()
) and
super.isLast()
)
}
final override predicate last(AstNode last, Completion c) {
// Last element of `catch` block
last(super.getBlock(), last, c)
or
not super.isLast() and
(
// Incompatible exception type: clause itself
last = this and
c.(MatchingCompletion).isNonMatch()
or
// Incompatible filter
this.lastFilterClause(last, c) and
c instanceof FalseCompletion
)
or
// Last `catch` clause inherits throw completions from the `try` block,
// when the clause does not match
super.isLast() and
c =
any(NestedCompletion nc |
nc.getNestLevel() = 0 and
this.throwMayBeUncaught(nc.getOuterCompletion()) and
(
// Incompatible exception type: clause itself
last = this and
nc.getInnerCompletion() =
any(MatchingCompletion mc |
mc.isNonMatch() and
mc.isValidFor(this)
)
or
// Incompatible filter
this.lastFilterClause(last, nc.getInnerCompletion().(FalseCompletion))
)
)
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
// Flow from catch clause to variable declaration/filter clause/block
pred = this and
c.(MatchingCompletion).isMatch() and
exists(AstNode next | first(next, succ) |
if exists(super.getVariableDeclExpr())
then next = super.getVariableDeclExpr()
else
if exists(super.getFilterClause())
then next = super.getFilterClause()
else next = super.getBlock()
)
or
// Flow from variable declaration to filter clause/block
last(super.getVariableDeclExpr(), pred, c) and
c instanceof SimpleCompletion and
exists(AstNode next | first(next, succ) |
if exists(super.getFilterClause())
then next = super.getFilterClause()
else next = super.getBlock()
)
or
// Flow from filter to block
last(super.getFilterClause(), pred, c) and
c instanceof TrueCompletion and
first(super.getBlock(), succ)
}
}
private class JumpStmtTree extends PostOrderTree instanceof JumpStmt {
final override predicate propagatesAbnormal(AstNode child) { child = this.getChild(0) }
final override predicate first(AstNode first) {
first(this.getChild(0), first)
or
not exists(this.getChild(0)) and first = this
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
last(this.getChild(0), pred, c) and
succ = this and
c instanceof NormalCompletion
}
}
pragma[nomagic]
private predicate goto(ControlFlowElement cfe, GotoCompletion gc, string label, Callable enclosing) {
last(_, cfe, gc) and
// Special case: when a `goto` happens inside a try statement with a
// finally block, flow does not go directly to the target, but instead
// to the finally block (and from there possibly to the target)
not cfe =
any(Statements::TryStmtTree t | t.(TryStmt).hasFinally()).getAFinallyPredecessor(_, true) and
label = gc.getLabel() and
enclosing = cfe.getEnclosingCallable()
}
private class LabeledStmtTree extends PreOrderTree instanceof LabeledStmt {
final override predicate propagatesAbnormal(AstNode child) { none() }
final override predicate last(AstNode last, Completion c) {
if this instanceof DefaultCase
then last(super.getStmt(), last, c)
else (
not this instanceof CaseStmt and
last = this and
c.isValidFor(this)
)
}
pragma[noinline]
predicate hasLabelInCallable(string label, Callable c) {
super.getEnclosingCallable() = c and
label = super.getLabel()
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
this instanceof DefaultCase and
pred = this and
first(super.getStmt(), succ) and
c instanceof SimpleCompletion
or
// Flow from element with matching `goto` completion to this statement
exists(string label, Callable enclosing |
goto(pred, c, label, enclosing) and
this.hasLabelInCallable(label, enclosing) and
succ = this
)
}
}
}
/** A control flow element that is split into multiple control flow nodes. */
class SplitAstNode extends AstNode, ControlFlowElement {
SplitAstNode() { strictcount(this.getAControlFlowNode()) > 1 }
}