mirror of
https://github.com/github/codeql.git
synced 2025-12-26 21:56:39 +01:00
1339 lines
42 KiB
Plaintext
1339 lines
42 KiB
Plaintext
/**
|
|
* Provides auxiliary classes and predicates used to construct the basic successor
|
|
* relation on control flow elements.
|
|
*
|
|
* 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`, are divided into three categories:
|
|
*
|
|
* 1. The in-going edge that points to the first CFG node to execute when
|
|
* `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 `k = last(n, _)`,
|
|
* 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 second 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`.
|
|
*/
|
|
|
|
private import codeql_ruby.ast.internal.TreeSitter::Generated
|
|
private import AstNodes
|
|
private import codeql_ruby.ast.internal.Variable
|
|
private import codeql_ruby.controlflow.ControlFlowGraph
|
|
private import Completion
|
|
private import SuccessorTypes
|
|
private import Splitting
|
|
private import codeql.files.FileSystem
|
|
|
|
module CfgScope {
|
|
abstract class Range_ extends AstNode {
|
|
abstract predicate entry(AstNode first);
|
|
|
|
abstract predicate exit(AstNode last, Completion c);
|
|
}
|
|
|
|
private class ProgramScope extends Range_, Program {
|
|
final override predicate entry(AstNode first) { first(this, first) }
|
|
|
|
final override predicate exit(AstNode last, Completion c) { last(this, last, c) }
|
|
}
|
|
|
|
private class BeginBlockScope extends Range_, BeginBlock {
|
|
final override predicate entry(AstNode first) {
|
|
first(this.(Trees::BeginBlockTree).getFirstChildNode(), first)
|
|
}
|
|
|
|
final override predicate exit(AstNode last, Completion c) {
|
|
last(this.(Trees::BeginBlockTree).getLastChildNode(), last, c)
|
|
}
|
|
}
|
|
|
|
private class EndBlockScope extends Range_, EndBlock {
|
|
final override predicate entry(AstNode first) {
|
|
first(this.(Trees::EndBlockTree).getFirstChildNode(), first)
|
|
}
|
|
|
|
final override predicate exit(AstNode last, Completion c) {
|
|
last(this.(Trees::EndBlockTree).getLastChildNode(), last, c)
|
|
}
|
|
}
|
|
|
|
private class MethodScope extends Range_, AstNode {
|
|
MethodScope() { this instanceof Method }
|
|
|
|
final override predicate entry(AstNode first) {
|
|
this.(Trees::RescueEnsureBlockTree).firstInner(first)
|
|
}
|
|
|
|
final override predicate exit(AstNode last, Completion c) {
|
|
this.(Trees::RescueEnsureBlockTree).lastInner(last, c)
|
|
}
|
|
}
|
|
|
|
private class SingletonMethodScope extends Range_, AstNode {
|
|
SingletonMethodScope() { this instanceof SingletonMethod }
|
|
|
|
final override predicate entry(AstNode first) {
|
|
this.(Trees::RescueEnsureBlockTree).firstInner(first)
|
|
}
|
|
|
|
final override predicate exit(AstNode last, Completion c) {
|
|
this.(Trees::RescueEnsureBlockTree).lastInner(last, c)
|
|
}
|
|
}
|
|
|
|
private class DoBlockScope extends Range_, DoBlock {
|
|
DoBlockScope() { not this.getParent() instanceof Lambda }
|
|
|
|
final override predicate entry(AstNode first) {
|
|
this.(Trees::RescueEnsureBlockTree).firstInner(first)
|
|
}
|
|
|
|
final override predicate exit(AstNode last, Completion c) {
|
|
this.(Trees::RescueEnsureBlockTree).lastInner(last, c)
|
|
}
|
|
}
|
|
|
|
private class BlockScope extends Range_, Block {
|
|
BlockScope() { not this.getParent() instanceof Lambda }
|
|
|
|
final override predicate entry(AstNode first) {
|
|
first(this.(Trees::BlockTree).getFirstChildNode(), first)
|
|
}
|
|
|
|
final override predicate exit(AstNode last, Completion c) {
|
|
last(this.(Trees::BlockTree).getLastChildNode(), last, c)
|
|
}
|
|
}
|
|
|
|
private class LambdaScope extends Range_, Lambda {
|
|
final override predicate entry(AstNode first) {
|
|
first(this.getParameters(), first)
|
|
or
|
|
not exists(this.getParameters()) and
|
|
(
|
|
this.getBody().(Trees::DoBlockTree).firstInner(first)
|
|
or
|
|
first(this.getBody().(Trees::BlockTree).getFirstChildNode(), first)
|
|
)
|
|
}
|
|
|
|
final override predicate exit(AstNode last, Completion c) {
|
|
last(this.getParameters(), last, c) and
|
|
not c instanceof NormalCompletion
|
|
or
|
|
last(this.getBody().(Trees::BlockTree).getLastChildNode(), last, c)
|
|
or
|
|
this.getBody().(Trees::RescueEnsureBlockTree).lastInner(last, c)
|
|
or
|
|
not exists(this.getBody()) and last(this.getParameters(), last, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
private AstNode parent(AstNode n) {
|
|
result = parentOf(n) and
|
|
not n instanceof CfgScope
|
|
}
|
|
|
|
abstract private class ControlFlowTree extends AstNode {
|
|
/**
|
|
* Holds if `first` is the first element executed within this AST node.
|
|
*/
|
|
pragma[nomagic]
|
|
abstract predicate first(AstNode first);
|
|
|
|
/**
|
|
* Holds if `last` with completion `c` is a potential last element executed
|
|
* within this AST node.
|
|
*/
|
|
pragma[nomagic]
|
|
abstract predicate last(AstNode last, Completion c);
|
|
|
|
/** Holds if abnormal execution of `child` should propagate upwards. */
|
|
abstract predicate propagatesAbnormal(AstNode child);
|
|
|
|
/**
|
|
* Holds if `succ` is a control flow successor for `pred`, given that `pred`
|
|
* finishes with completion `c`.
|
|
*/
|
|
pragma[nomagic]
|
|
abstract predicate succ(AstNode pred, AstNode succ, Completion c);
|
|
|
|
/**
|
|
* Holds if this node should be hidden in the CFG. That is, edges
|
|
* `pred -> this -> succ` are converted to a single edge `pred -> succ`.
|
|
*/
|
|
predicate isHidden() { none() }
|
|
}
|
|
|
|
/** Holds if `first` is the first element executed within AST node `n`. */
|
|
predicate first(ControlFlowTree n, AstNode first) { n.first(first) }
|
|
|
|
/**
|
|
* Holds if `last` with completion `c` is a potential last element executed
|
|
* within AST node `n`.
|
|
*/
|
|
predicate last(ControlFlowTree n, AstNode last, Completion c) {
|
|
n.last(last, c)
|
|
or
|
|
exists(AstNode child |
|
|
n.propagatesAbnormal(child) and
|
|
last(child, last, c) and
|
|
not c instanceof NormalCompletion
|
|
)
|
|
}
|
|
|
|
private predicate succImpl(AstNode pred, AstNode succ, Completion c) {
|
|
any(ControlFlowTree cft).succ(pred, succ, c)
|
|
}
|
|
|
|
private predicate isHidden(ControlFlowTree t) {
|
|
t.isHidden()
|
|
or
|
|
t = any(Method m).getName()
|
|
or
|
|
t = any(SingletonMethod m).getName()
|
|
or
|
|
t = any(Call c).getMethod() and
|
|
not t instanceof ScopeResolution
|
|
or
|
|
t instanceof RestAssignment
|
|
or
|
|
t instanceof Superclass
|
|
}
|
|
|
|
private predicate succImplIfHidden(AstNode pred, AstNode succ) {
|
|
isHidden(pred) and
|
|
succImpl(pred, succ, any(SimpleCompletion c))
|
|
}
|
|
|
|
/**
|
|
* Holds if `succ` is a control flow successor for `pred`, given that `pred`
|
|
* finishes with completion `c`.
|
|
*/
|
|
pragma[nomagic]
|
|
predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
exists(AstNode n |
|
|
succImpl(pred, n, c) and
|
|
succImplIfHidden*(n, succ) and
|
|
not isHidden(pred) and
|
|
not isHidden(succ)
|
|
)
|
|
}
|
|
|
|
/** Holds if `first` is first executed when entering `scope`. */
|
|
pragma[nomagic]
|
|
predicate succEntry(CfgScope::Range_ scope, AstNode first) {
|
|
exists(AstNode n |
|
|
scope.entry(n) and
|
|
succImplIfHidden*(n, first) and
|
|
not isHidden(first)
|
|
)
|
|
}
|
|
|
|
/** Holds if `last` with completion `c` can exit `scope`. */
|
|
pragma[nomagic]
|
|
predicate succExit(CfgScope::Range_ scope, AstNode last, Completion c) {
|
|
exists(AstNode n |
|
|
scope.exit(n, c) and
|
|
succImplIfHidden*(last, n) and
|
|
not isHidden(last)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* An AST node where the children are evaluated following a standard left-to-right
|
|
* evaluation. The actual evaluation order is determined by the predicate
|
|
* `getChildNode()`.
|
|
*/
|
|
abstract private class StandardNode extends ControlFlowTree {
|
|
/** Gets the `i`th child node, in order of evaluation. */
|
|
ControlFlowTree getChildNode(int i) {
|
|
result = this.getAFieldOrChild() and
|
|
i = result.getParentIndex()
|
|
}
|
|
|
|
private AstNode getChildNodeRanked(int i) {
|
|
result = rank[i + 1](AstNode child, int j | child = this.getChildNode(j) | child order by j)
|
|
}
|
|
|
|
/** Gets the first child node of this element. */
|
|
final AstNode getFirstChildNode() { result = this.getChildNodeRanked(0) }
|
|
|
|
/** Gets the last child node of this node. */
|
|
final AstNode getLastChildNode() {
|
|
exists(int last |
|
|
result = this.getChildNodeRanked(last) and
|
|
not exists(this.getChildNodeRanked(last + 1))
|
|
)
|
|
}
|
|
|
|
override predicate propagatesAbnormal(AstNode child) { child = this.getChildNode(_) }
|
|
|
|
pragma[nomagic]
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
exists(int i |
|
|
last(this.getChildNodeRanked(i), pred, c) and
|
|
c instanceof NormalCompletion and
|
|
first(this.getChildNodeRanked(i + 1), succ)
|
|
)
|
|
}
|
|
}
|
|
|
|
abstract private class PreOrderTree extends ControlFlowTree {
|
|
final override predicate first(AstNode first) { first = this }
|
|
}
|
|
|
|
abstract private class StandardPreOrderTree extends StandardNode, PreOrderTree {
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getLastChildNode(), last, c)
|
|
or
|
|
not exists(this.getLastChildNode()) and
|
|
c.isValidFor(this) and
|
|
last = this
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
StandardNode.super.succ(pred, succ, c)
|
|
or
|
|
pred = this and
|
|
first(this.getFirstChildNode(), succ) and
|
|
c instanceof SimpleCompletion
|
|
}
|
|
}
|
|
|
|
abstract private class PostOrderTree extends ControlFlowTree {
|
|
override predicate last(AstNode last, Completion c) {
|
|
last = this and
|
|
c.isValidFor(last)
|
|
}
|
|
}
|
|
|
|
private class LeftToRightPostOrderNodes =
|
|
@argument_list or @array or @bare_string or @bare_symbol or @binary or @block_argument or
|
|
@break or @call or @chained_string or @delimited_symbol or @destructured_left_assignment or
|
|
@destructured_parameter or @element_reference or @exception_variable or @hash or
|
|
@hash_splat_argument or @interpolation or @left_assignment_list or @next or
|
|
@operator_assignment or @pair or @parenthesized_statements or @range or @redo or @regex or
|
|
@rest_assignment or @retry or @return or @right_assignment_list or @scope_resolution or
|
|
@token_simple_symbol or @splat_argument or @string__ or @string_array or @subshell or
|
|
@superclass or @symbol_array or @token_hash_key_symbol or @unary;
|
|
|
|
private class LeftToRightPostOrderTree extends StandardPostOrderTree, LeftToRightPostOrderNodes {
|
|
LeftToRightPostOrderTree() {
|
|
not this instanceof LogicalNotAstNode and
|
|
not this instanceof LogicalAndAstNode and
|
|
not this instanceof LogicalOrAstNode
|
|
}
|
|
|
|
override predicate isHidden() {
|
|
this instanceof ArgumentList or
|
|
this instanceof ChainedString or
|
|
this instanceof ExceptionVariable or
|
|
this instanceof LeftAssignmentList or
|
|
this instanceof RightAssignmentList
|
|
}
|
|
}
|
|
|
|
private class LeftToRightPreOrderNodes =
|
|
@alias or @block_parameters or @class or @do or @else or @ensure or @lambda_parameters or
|
|
@method_parameters or @pattern or @program or @then or @undef or @yield;
|
|
|
|
private class LeftToRightPreOrderTree extends StandardPreOrderTree, LeftToRightPreOrderNodes {
|
|
override predicate isHidden() {
|
|
this instanceof BlockParameters or
|
|
this instanceof Do or
|
|
this instanceof Else or
|
|
this instanceof LambdaParameters or
|
|
this instanceof MethodParameters or
|
|
this instanceof Pattern or
|
|
this instanceof Program or
|
|
this instanceof Then
|
|
}
|
|
}
|
|
|
|
abstract private class StandardPostOrderTree extends StandardNode, PostOrderTree {
|
|
final override predicate first(AstNode first) {
|
|
first(this.getFirstChildNode(), first)
|
|
or
|
|
not exists(this.getFirstChildNode()) and
|
|
first = this
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
StandardNode.super.succ(pred, succ, c)
|
|
or
|
|
last(this.getLastChildNode(), pred, c) and
|
|
succ = this and
|
|
c instanceof NormalCompletion
|
|
}
|
|
}
|
|
|
|
abstract private class LeafTree extends PreOrderTree, PostOrderTree {
|
|
override predicate propagatesAbnormal(AstNode child) { none() }
|
|
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
|
|
}
|
|
|
|
abstract class ScopeTree extends StandardNode, LeafTree {
|
|
final override predicate propagatesAbnormal(AstNode child) { none() }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
StandardNode.super.succ(pred, succ, c)
|
|
}
|
|
}
|
|
|
|
/** Defines the CFG by dispatch on the various AST types. */
|
|
module Trees {
|
|
private class AssignmentTree extends StandardPostOrderTree, Assignment {
|
|
final override ControlFlowTree getChildNode(int i) {
|
|
result = this.getRight() and i = 0
|
|
or
|
|
result = this.getLeft() and i = 1
|
|
}
|
|
}
|
|
|
|
private class BeginTree extends RescueEnsureBlockTree, PreOrderTree, Begin {
|
|
final override AstNode getChildNode(int i, boolean rescuable) {
|
|
result = this.getChild(i) and rescuable = true
|
|
}
|
|
|
|
final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) }
|
|
|
|
override predicate isHidden() { any() }
|
|
}
|
|
|
|
class BeginBlockTree extends ScopeTree, BeginBlock {
|
|
final override ControlFlowTree getChildNode(int i) { result = this.getChild(i) }
|
|
}
|
|
|
|
class BlockTree extends ScopeTree, Block {
|
|
final override ControlFlowTree getChildNode(int i) {
|
|
result = this.getParameters() and i = 0
|
|
or
|
|
result = this.getChild(i - 1)
|
|
}
|
|
}
|
|
|
|
private class BlockParameterTree extends LeafTree, BlockParameter { }
|
|
|
|
private class CaseTree extends PreOrderTree, Case {
|
|
final override predicate propagatesAbnormal(AstNode child) {
|
|
child = this.getValue() or child = this.getChild(_)
|
|
}
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getValue(), last, c) and not exists(this.getChild(_))
|
|
or
|
|
last(this.getChild(_).(When).getBody(), last, c)
|
|
or
|
|
exists(int i, ControlFlowTree lastBranch |
|
|
lastBranch = this.getChild(i) and
|
|
not exists(this.getChild(i + 1)) and
|
|
last(lastBranch, last, c)
|
|
)
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
exists(AstNode next |
|
|
pred = this and
|
|
first(next, succ) and
|
|
c instanceof SimpleCompletion
|
|
|
|
|
next = this.getValue()
|
|
or
|
|
not exists(this.getValue()) and
|
|
next = this.getChild(0)
|
|
)
|
|
or
|
|
last(this.getValue(), pred, c) and
|
|
first(this.getChild(0), succ) and
|
|
c instanceof SimpleCompletion
|
|
or
|
|
exists(int i, WhenTree branch | branch = this.getChild(i) |
|
|
last(branch.getLastPattern(), pred, c) and
|
|
first(this.getChild(i + 1), succ) and
|
|
c.(ConditionalCompletion).getValue() = false
|
|
)
|
|
}
|
|
}
|
|
|
|
private class CharacterTree extends LeafTree, Character { }
|
|
|
|
private class ClassTree extends RescueEnsureBlockTree, PreOrderTree, Class {
|
|
final override AstNode getChildNode(int i, boolean rescuable) {
|
|
result = this.getName() and i = 0 and rescuable = false
|
|
or
|
|
result = this.getSuperclass() and i = 1 and rescuable = true
|
|
or
|
|
result = this.getChild(i - 2) and rescuable = true
|
|
}
|
|
|
|
final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) }
|
|
}
|
|
|
|
private class ClassVariableTree extends LeafTree, ClassVariable { }
|
|
|
|
private class ComplexTree extends LeafTree, Complex { }
|
|
|
|
private class ConstantTree extends LeafTree, Constant { }
|
|
|
|
/** A parameter that may have a default value. */
|
|
abstract class DefaultValueParameterTree extends PreOrderTree {
|
|
abstract AstNode getDefaultValue();
|
|
|
|
predicate hasDefaultValue() { exists(this.getDefaultValue()) }
|
|
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getDefaultValue() }
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getDefaultValue(), last, c) and
|
|
c instanceof NormalCompletion
|
|
or
|
|
last = this and
|
|
(
|
|
not this.hasDefaultValue() and
|
|
c instanceof SimpleCompletion
|
|
or
|
|
this.hasDefaultValue() and
|
|
c.(MatchingCompletion).getValue() = true
|
|
)
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
pred = this and
|
|
first(this.getDefaultValue(), succ) and
|
|
c.(MatchingCompletion).getValue() = false
|
|
}
|
|
}
|
|
|
|
class DoBlockTree extends RescueEnsureBlockTree, PostOrderTree, DoBlock {
|
|
final override predicate first(AstNode first) { first = this }
|
|
|
|
final override AstNode getChildNode(int i, boolean rescuable) {
|
|
result = this.getParameters() and i = 0 and rescuable = false
|
|
or
|
|
result = this.getChild(i - 1) and rescuable = true
|
|
}
|
|
}
|
|
|
|
private class EmptyStatementTree extends LeafTree, EmptyStatement { }
|
|
|
|
class EndBlockTree extends ScopeTree, EndBlock {
|
|
final override ControlFlowTree getChildNode(int i) { result = this.getChild(i) }
|
|
}
|
|
|
|
private class ExceptionsTree extends PreOrderTree, Exceptions {
|
|
final override predicate propagatesAbnormal(AstNode child) { none() }
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getChild(_), last, c) and
|
|
c.(MatchingCompletion).getValue() = true
|
|
or
|
|
exists(int lst |
|
|
last(this.getChild(lst), last, c) and
|
|
not exists(this.getChild(lst + 1))
|
|
)
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
pred = this and
|
|
first(this.getChild(0), succ) and
|
|
c instanceof SimpleCompletion
|
|
or
|
|
exists(int i |
|
|
last(this.getChild(i), pred, c) and
|
|
c.(MatchingCompletion).getValue() = false and
|
|
first(this.getChild(i + 1), succ)
|
|
)
|
|
}
|
|
|
|
override predicate isHidden() { any() }
|
|
}
|
|
|
|
private class FalseTree extends LeafTree, False { }
|
|
|
|
private class FloatTree extends LeafTree, Float { }
|
|
|
|
/**
|
|
* Control flow of a for-in loop
|
|
*
|
|
* For example, this program fragment:
|
|
*
|
|
* ```rb
|
|
* for arg in args do
|
|
* puts arg
|
|
* end
|
|
* puts "done";
|
|
* ```
|
|
*
|
|
* has the following control flow graph:
|
|
*
|
|
* ```
|
|
* args
|
|
* |
|
|
* in------<-----
|
|
* / \ \
|
|
* / \ |
|
|
* / \ |
|
|
* / \ |
|
|
* empty non-empty |
|
|
* | \ |
|
|
* for \ |
|
|
* | arg |
|
|
* | | |
|
|
* puts "done" puts arg |
|
|
* \___/
|
|
* ```
|
|
*/
|
|
private class ForTree extends PostOrderTree, For {
|
|
final override predicate propagatesAbnormal(AstNode child) {
|
|
child = this.getPattern() or child = this.getArray()
|
|
}
|
|
|
|
final override predicate first(AstNode first) { first(this.getArray(), first) }
|
|
|
|
private In getIn() { result = this.getValue() }
|
|
|
|
private UnderscoreArg getArray() { result = this.getValue().getChild() }
|
|
|
|
/**
|
|
* for pattern in array do body end
|
|
* ```
|
|
* array +-> in +--[non empty]--> pattern -> body -> in
|
|
* |--[empty]--> for
|
|
* ```
|
|
*/
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
last(this.getArray(), pred, c) and
|
|
first(this.getIn(), succ) and
|
|
c instanceof SimpleCompletion
|
|
or
|
|
last(this.getIn(), pred, c) and
|
|
first(this.getPattern(), succ) and
|
|
c.(EmptinessCompletion).getValue() = false
|
|
or
|
|
last(this.getPattern(), pred, c) and
|
|
first(this.getBody(), succ) and
|
|
c instanceof NormalCompletion
|
|
or
|
|
last(this.getBody(), pred, c) and
|
|
first(this.getIn(), succ) and
|
|
c.continuesLoop()
|
|
or
|
|
last(this.getBody(), pred, c) and
|
|
first(this.getBody(), succ) and
|
|
c instanceof RedoCompletion
|
|
or
|
|
succ = this and
|
|
(
|
|
last(this.getIn(), pred, c) and
|
|
c.(EmptinessCompletion).getValue() = true
|
|
or
|
|
last(this.getBody(), pred, c) and
|
|
not c.continuesLoop() and
|
|
not c instanceof BreakCompletion and
|
|
not c instanceof RedoCompletion
|
|
or
|
|
last(this.getBody(), pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
|
|
)
|
|
}
|
|
}
|
|
|
|
private class GlobalVariableTree extends LeafTree, GlobalVariable { }
|
|
|
|
private class HashSplatParameterTree extends LeafTree, HashSplatParameter { }
|
|
|
|
private HeredocBody heredoc(HeredocBeginning start) {
|
|
exists(int i, File f |
|
|
start =
|
|
rank[i](HeredocBeginning b |
|
|
f = b.getLocation().getFile()
|
|
|
|
|
b order by b.getLocation().getStartLine(), b.getLocation().getStartColumn()
|
|
) and
|
|
result =
|
|
rank[i](HeredocBody b |
|
|
f = b.getLocation().getFile()
|
|
|
|
|
b order by b.getLocation().getStartLine(), b.getLocation().getStartColumn()
|
|
)
|
|
)
|
|
}
|
|
|
|
private class HeredocBeginningTree extends StandardPreOrderTree, HeredocBeginning {
|
|
final override ControlFlowTree getChildNode(int i) { result = heredoc(this).getChild(i) }
|
|
}
|
|
|
|
private class IdentifierTree extends LeafTree, Identifier { }
|
|
|
|
private class IfElsifTree extends PostOrderTree, IfElsifAstNode {
|
|
final override predicate propagatesAbnormal(AstNode child) {
|
|
child = this.getConditionNode() or child = this.getBranch(_)
|
|
}
|
|
|
|
final override predicate first(AstNode first) { first(this.getConditionNode(), first) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
exists(boolean b |
|
|
last(this.getConditionNode(), pred, c) and
|
|
b = c.(BooleanCompletion).getValue()
|
|
|
|
|
first(this.getBranch(b), succ)
|
|
or
|
|
not exists(this.getBranch(b)) and
|
|
succ = this
|
|
)
|
|
or
|
|
last(this.getBranch(_), pred, c) and
|
|
succ = this and
|
|
c instanceof NormalCompletion
|
|
}
|
|
}
|
|
|
|
private class InTree extends LeafTree, In { }
|
|
|
|
private class InstanceVariableTree extends LeafTree, InstanceVariable { }
|
|
|
|
private class IntegerTree extends LeafTree, Integer { }
|
|
|
|
private class KeywordParameterTree extends DefaultValueParameterTree, KeywordParameter {
|
|
final override AstNode getDefaultValue() { result = this.getValue() }
|
|
}
|
|
|
|
class LambdaTree extends LeafTree, Lambda {
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
last(this.getParameters(), pred, c) and
|
|
c instanceof NormalCompletion and
|
|
(
|
|
this.getBody().(DoBlockTree).firstInner(succ)
|
|
or
|
|
first(this.getBody().(BlockTree).getFirstChildNode(), succ)
|
|
)
|
|
}
|
|
}
|
|
|
|
class LogicalAndTree extends PostOrderTree, LogicalAndAstNode {
|
|
final override predicate propagatesAbnormal(AstNode child) { child in [left, right] }
|
|
|
|
final override predicate first(AstNode first) { first(left, first) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
last(left, pred, c) and
|
|
c instanceof TrueCompletion and
|
|
first(right, succ)
|
|
or
|
|
last(left, pred, c) and
|
|
c instanceof FalseCompletion and
|
|
succ = this
|
|
or
|
|
last(right, pred, c) and
|
|
c instanceof NormalCompletion and
|
|
succ = this
|
|
}
|
|
}
|
|
|
|
class LogicalOrTree extends PostOrderTree, LogicalOrAstNode {
|
|
final override predicate propagatesAbnormal(AstNode child) { child in [left, right] }
|
|
|
|
final override predicate first(AstNode first) { first(left, first) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
last(left, pred, c) and
|
|
c instanceof FalseCompletion and
|
|
first(right, succ)
|
|
or
|
|
last(left, pred, c) and
|
|
c instanceof TrueCompletion and
|
|
succ = this
|
|
or
|
|
last(right, pred, c) and
|
|
c instanceof NormalCompletion and
|
|
succ = this
|
|
}
|
|
}
|
|
|
|
class LogicalNotTree extends PostOrderTree, LogicalNotAstNode {
|
|
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 MethodTree extends RescueEnsureBlockTree, PostOrderTree, Method {
|
|
final override AstNode getChildNode(int i, boolean rescuable) {
|
|
result = this.getParameters() and i = 0 and rescuable = false
|
|
or
|
|
result = this.getChild(i - 1) and rescuable = true
|
|
}
|
|
|
|
final override predicate first(AstNode first) { first(this.getName(), first) }
|
|
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
RescueEnsureBlockTree.super.succ(pred, succ, c)
|
|
or
|
|
last(this.getName(), pred, c) and
|
|
succ = this and
|
|
c instanceof NormalCompletion
|
|
}
|
|
}
|
|
|
|
private class ModuleTree extends RescueEnsureBlockTree, PreOrderTree, Module {
|
|
final override AstNode getChildNode(int i, boolean rescuable) {
|
|
result = this.getName() and i = 0 and rescuable = false
|
|
or
|
|
result = this.getChild(i - 1) and rescuable = true
|
|
}
|
|
|
|
final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) }
|
|
}
|
|
|
|
private class NilTree extends LeafTree, Nil { }
|
|
|
|
private class OptionalParameterTree extends DefaultValueParameterTree, OptionalParameter {
|
|
final override AstNode getDefaultValue() { result = this.getValue() }
|
|
}
|
|
|
|
private class RationalTree extends LeafTree, Rational { }
|
|
|
|
private class RescueTree extends PreOrderTree, Rescue {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getExceptions() }
|
|
|
|
predicate lastMatch(AstNode last, Completion c) {
|
|
last(this.getBody(), last, c)
|
|
or
|
|
not exists(this.getBody()) and
|
|
(
|
|
last(this.getVariable(), last, c)
|
|
or
|
|
not exists(this.getVariable()) and
|
|
(
|
|
last(this.getExceptions(), last, c) and
|
|
c.(MatchingCompletion).getValue() = true
|
|
or
|
|
not exists(this.getExceptions()) and
|
|
last = this and
|
|
c.isValidFor(this)
|
|
)
|
|
)
|
|
}
|
|
|
|
predicate lastNoMatch(AstNode last, Completion c) {
|
|
last(this.getExceptions(), last, c) and
|
|
c.(MatchingCompletion).getValue() = false
|
|
}
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
this.lastNoMatch(last, c)
|
|
or
|
|
this.lastMatch(last, c)
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
exists(AstNode next |
|
|
pred = this and
|
|
first(next, succ) and
|
|
c instanceof SimpleCompletion
|
|
|
|
|
next = this.getExceptions()
|
|
or
|
|
not exists(this.getExceptions()) and
|
|
(
|
|
next = this.getVariable()
|
|
or
|
|
not exists(this.getVariable()) and
|
|
next = this.getBody()
|
|
)
|
|
)
|
|
or
|
|
exists(AstNode next |
|
|
last(this.getExceptions(), pred, c) and
|
|
first(next, succ) and
|
|
c.(MatchingCompletion).getValue() = true
|
|
|
|
|
next = this.getVariable()
|
|
or
|
|
not exists(this.getVariable()) and
|
|
next = this.getBody()
|
|
)
|
|
or
|
|
last(this.getVariable(), pred, c) and
|
|
first(this.getBody(), succ) and
|
|
c instanceof NormalCompletion
|
|
}
|
|
}
|
|
|
|
/** Gets a child of `n` that is in CFG scope `scope`. */
|
|
pragma[noinline]
|
|
private AstNode getAChildInScope(AstNode n, CfgScope scope) {
|
|
result.getParent() = n and
|
|
scope = getCfgScope(result)
|
|
}
|
|
|
|
/** A block that may contain `rescue`/`ensure`. */
|
|
abstract class RescueEnsureBlockTree extends ControlFlowTree {
|
|
/**
|
|
* Gets the `i`th child of this block. `rescuable` indicates whether exceptional
|
|
* execution of the child can be caught by `rescue`/`ensure`.
|
|
*/
|
|
abstract AstNode getChildNode(int i, boolean rescuable);
|
|
|
|
/** Gets the `i`th child in the body of this block. */
|
|
final private AstNode getBodyChild(int i, boolean rescuable) {
|
|
result = this.getChildNode(_, rescuable) and
|
|
result =
|
|
rank[i + 1](AstNode child, int j |
|
|
child = this.getChildNode(j, _) and
|
|
not result instanceof Rescue and
|
|
not result instanceof Ensure and
|
|
not result instanceof Else
|
|
|
|
|
child order by j
|
|
)
|
|
}
|
|
|
|
/** Gets the `i`th `rescue` block in this block. */
|
|
final Rescue getRescue(int i) {
|
|
result = rank[i + 1](Rescue s | s = this.getAFieldOrChild() | s order by s.getParentIndex())
|
|
}
|
|
|
|
/** Gets the `else` block in this block, if any. */
|
|
final private Else getElse() { result = unique(Else s | s = this.getAFieldOrChild()) }
|
|
|
|
/** Gets the `ensure` block in this block, if any. */
|
|
final Ensure getEnsure() { result = unique(Ensure s | s = this.getAFieldOrChild()) }
|
|
|
|
final private predicate hasEnsure() { exists(this.getEnsure()) }
|
|
|
|
final override predicate propagatesAbnormal(AstNode child) { none() }
|
|
|
|
/**
|
|
* Gets a descendant that belongs to the `ensure` block of this block, if any.
|
|
* Nested `ensure` blocks are not included.
|
|
*/
|
|
AstNode getAnEnsureDescendant() {
|
|
result = this.getEnsure()
|
|
or
|
|
exists(AstNode mid |
|
|
mid = this.getAnEnsureDescendant() and
|
|
result = getAChildInScope(mid, getCfgScope(mid)) and
|
|
not exists(RescueEnsureBlockTree nestedBlock |
|
|
result = nestedBlock.getEnsure() and
|
|
nestedBlock != this
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `innerBlock` has an `ensure` block and is immediately nested inside the
|
|
* `ensure` block of this block.
|
|
*/
|
|
private predicate nestedEnsure(RescueEnsureBlockTree innerBlock) {
|
|
exists(Ensure innerEnsure |
|
|
innerEnsure = getAChildInScope(this.getAnEnsureDescendant(), getCfgScope(this)) and
|
|
innerEnsure = innerBlock.getEnsure()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the `ensure`-nesting level of this block. That is, the number of `ensure`
|
|
* blocks that this block is nested under.
|
|
*/
|
|
int nestLevel() { result = count(RescueEnsureBlockTree outer | outer.nestedEnsure+(this)) }
|
|
|
|
/**
|
|
* Holds if `last` is a last element in the body of this block. `ensurable`
|
|
* indicates whether `last` may be a predecessor of an `ensure` block.
|
|
*/
|
|
pragma[nomagic]
|
|
private predicate lastBody(AstNode last, Completion c, boolean ensurable) {
|
|
exists(boolean rescuable |
|
|
if c instanceof RaiseCompletion then ensurable = rescuable else ensurable = true
|
|
|
|
|
last(this.getBodyChild(_, rescuable), last, c) and
|
|
not c instanceof NormalCompletion
|
|
or
|
|
exists(int lst |
|
|
last(this.getBodyChild(lst, rescuable), last, c) and
|
|
not exists(this.getBodyChild(lst + 1, _))
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a last element from this block that may finish with completion `c`, such
|
|
* that control may be transferred to the `ensure` block (if it exists), but only
|
|
* if `ensurable = true`.
|
|
*/
|
|
pragma[nomagic]
|
|
private AstNode getAnEnsurePredecessor(Completion c, boolean ensurable) {
|
|
this.lastBody(result, c, ensurable) and
|
|
(
|
|
// Any non-throw completion will always continue directly to the `ensure` block,
|
|
// unless there is an `else` block
|
|
not c instanceof RaiseCompletion and
|
|
not exists(this.getElse())
|
|
or
|
|
// Any completion will continue to the `ensure` block when there are no `rescue`
|
|
// blocks
|
|
not exists(this.getRescue(_))
|
|
)
|
|
or
|
|
// Last element from any matching `rescue` block continues to the `ensure` block
|
|
this.getRescue(_).(RescueTree).lastMatch(result, c) and
|
|
ensurable = true
|
|
or
|
|
// If the last `rescue` block does not match, continue to the `ensure` block
|
|
exists(int lst, MatchingCompletion mc |
|
|
this.getRescue(lst).(RescueTree).lastNoMatch(result, mc) and
|
|
mc.getValue() = false and
|
|
not exists(this.getRescue(lst + 1)) and
|
|
c =
|
|
any(NestedEnsureCompletion nec |
|
|
nec.getOuterCompletion() instanceof RaiseCompletion and
|
|
nec.getInnerCompletion() = mc and
|
|
nec.getNestLevel() = 0
|
|
) and
|
|
ensurable = true
|
|
)
|
|
or
|
|
// Last element of `else` block continues to the `ensure` block
|
|
last(this.getElse(), result, c) and
|
|
ensurable = true
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate lastEnsure0(AstNode last, Completion c) { last(this.getEnsure(), last, c) }
|
|
|
|
pragma[nomagic]
|
|
private predicate lastEnsure(
|
|
AstNode last, NormalCompletion ensure, Completion outer, int nestLevel
|
|
) {
|
|
this.lastEnsure0(last, ensure) and
|
|
exists(
|
|
this.getAnEnsurePredecessor(any(Completion c0 | outer = c0.getOuterCompletion()), true)
|
|
) and
|
|
nestLevel = this.nestLevel()
|
|
}
|
|
|
|
predicate lastInner(AstNode last, Completion c) {
|
|
exists(boolean ensurable | last = this.getAnEnsurePredecessor(c, ensurable) |
|
|
not this.hasEnsure()
|
|
or
|
|
ensurable = false
|
|
)
|
|
or
|
|
// If the body completes normally, take the completion from the `ensure` block
|
|
this.lastEnsure(last, c, any(NormalCompletion nc), _)
|
|
or
|
|
// If the `ensure` block completes normally, it inherits any non-normal
|
|
// completion from the body
|
|
c =
|
|
any(NestedEnsureCompletion nec |
|
|
this.lastEnsure(last, nec.getAnInnerCompatibleCompletion(), nec.getOuterCompletion(),
|
|
nec.getNestLevel())
|
|
)
|
|
or
|
|
not exists(this.getBodyChild(_, _)) and
|
|
not exists(this.getRescue(_)) and
|
|
this.lastEnsure0(last, c)
|
|
or
|
|
last([this.getEnsure(), this.getBodyChild(_, false)], last, c) and
|
|
not c instanceof NormalCompletion
|
|
}
|
|
|
|
predicate firstInner(AstNode first) {
|
|
first(this.getBodyChild(0, _), first)
|
|
or
|
|
not exists(this.getBodyChild(_, _)) and
|
|
(
|
|
first(this.getRescue(_), first)
|
|
or
|
|
not exists(this.getRescue(_)) and
|
|
first(this.getEnsure(), first)
|
|
)
|
|
}
|
|
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
this instanceof PreOrderTree and
|
|
pred = this and
|
|
c instanceof SimpleCompletion and
|
|
this.firstInner(succ)
|
|
or
|
|
// Normal left-to-right evaluation in the body
|
|
exists(int i |
|
|
last(this.getBodyChild(i, _), pred, c) and
|
|
first(this.getBodyChild(i + 1, _), succ) and
|
|
c instanceof NormalCompletion
|
|
)
|
|
or
|
|
// Exceptional flow from body to first `rescue`
|
|
this.lastBody(pred, c, true) and
|
|
first(this.getRescue(0), succ) and
|
|
c instanceof RaiseCompletion
|
|
or
|
|
// Flow from one `rescue` clause to the next when there is no match
|
|
exists(RescueTree rescue, int i | rescue = this.getRescue(i) |
|
|
rescue.lastNoMatch(pred, c) and
|
|
first(this.getRescue(i + 1), succ)
|
|
)
|
|
or
|
|
// Flow from body to `else` block when no exception
|
|
this.lastBody(pred, c, _) and
|
|
first(this.getElse(), succ) and
|
|
c instanceof NormalCompletion
|
|
or
|
|
// Flow into `ensure` block
|
|
pred = getAnEnsurePredecessor(c, true) and
|
|
first(this.getEnsure(), succ)
|
|
}
|
|
}
|
|
|
|
private class RescueModifierTree extends PreOrderTree, RescueModifier {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getHandler() }
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getBody(), last, c) and
|
|
not c instanceof RaiseCompletion
|
|
or
|
|
last(this.getHandler(), last, c)
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
pred = this and
|
|
first(this.getBody(), succ) and
|
|
c instanceof SimpleCompletion
|
|
or
|
|
last(this.getBody(), pred, c) and
|
|
c instanceof RaiseCompletion and
|
|
first(this.getHandler(), succ)
|
|
}
|
|
}
|
|
|
|
private class SelfTree extends LeafTree, Self { }
|
|
|
|
private class SetterTree extends LeafTree, Setter { }
|
|
|
|
private class SingletonClassTree extends RescueEnsureBlockTree, PreOrderTree, SingletonClass {
|
|
final override AstNode getChildNode(int i, boolean rescuable) {
|
|
rescuable = true and
|
|
(
|
|
result = this.getValue() and i = 0
|
|
or
|
|
result = this.getChild(i - 1)
|
|
)
|
|
}
|
|
|
|
final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) }
|
|
}
|
|
|
|
private class SingletonMethodTree extends RescueEnsureBlockTree, PostOrderTree, SingletonMethod {
|
|
final override AstNode getChildNode(int i, boolean rescuable) {
|
|
result = this.getParameters() and
|
|
i = 0 and
|
|
rescuable = false
|
|
or
|
|
result = this.getChild(i - 1) and
|
|
rescuable = true
|
|
}
|
|
|
|
final override predicate first(AstNode first) { first(this.getObject(), first) }
|
|
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
RescueEnsureBlockTree.super.succ(pred, succ, c)
|
|
or
|
|
last(this.getObject(), pred, c) and
|
|
first(this.getName(), succ) and
|
|
c instanceof NormalCompletion
|
|
or
|
|
last(this.getName(), pred, c) and
|
|
succ = this and
|
|
c instanceof NormalCompletion
|
|
}
|
|
}
|
|
|
|
private class SplatParameterTree extends LeafTree, SplatParameter { }
|
|
|
|
private class SuperTree extends LeafTree, Super { }
|
|
|
|
private class TrueTree extends LeafTree, True { }
|
|
|
|
private class WhenTree extends PreOrderTree, When {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getPattern(_) }
|
|
|
|
final Pattern getLastPattern() {
|
|
exists(int i |
|
|
result = this.getPattern(i) and
|
|
not exists(this.getPattern(i + 1))
|
|
)
|
|
}
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getLastPattern(), last, c) and
|
|
c.(ConditionalCompletion).getValue() = false
|
|
or
|
|
last(this.getBody(), last, c)
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
pred = this and
|
|
first(this.getPattern(0), succ) and
|
|
c instanceof SimpleCompletion
|
|
or
|
|
exists(int i, Pattern p, boolean b |
|
|
p = this.getPattern(i) and
|
|
last(p, pred, c) and
|
|
b = c.(ConditionalCompletion).getValue()
|
|
|
|
|
b = true and
|
|
first(this.getBody(), succ)
|
|
or
|
|
b = false and
|
|
first(this.getPattern(i + 1), succ)
|
|
)
|
|
}
|
|
}
|
|
|
|
private class ConditionalLoopTree extends PostOrderTree, ConditionalLoopAstNode {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getConditionNode() }
|
|
|
|
final override predicate first(AstNode first) { first(this.getConditionNode(), first) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
last(this.getConditionNode(), pred, c) and
|
|
this.continueLoop(c) and
|
|
first(this.getBodyNode(), succ)
|
|
or
|
|
last(this.getBodyNode(), pred, c) and
|
|
first(this.getConditionNode(), succ) and
|
|
c.continuesLoop()
|
|
or
|
|
last(this.getBodyNode(), pred, c) and
|
|
first(this.getBodyNode(), succ) and
|
|
c instanceof RedoCompletion
|
|
or
|
|
succ = this and
|
|
(
|
|
last(this.getConditionNode(), pred, c) and
|
|
this.endLoop(c)
|
|
or
|
|
last(this.getBodyNode(), pred, c) and
|
|
not c.continuesLoop() and
|
|
not c instanceof BreakCompletion and
|
|
not c instanceof RedoCompletion
|
|
or
|
|
last(this.getBodyNode(), pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
cached
|
|
private module Cached {
|
|
/** Gets the CFG scope of node `n`. */
|
|
cached
|
|
CfgScope getCfgScope(AstNode n) { result = unique(CfgScope scope | scope = parent*(parentOf(n))) }
|
|
|
|
private predicate isAbnormalExitType(SuccessorType t) {
|
|
t instanceof RaiseSuccessor or t instanceof ExitSuccessor
|
|
}
|
|
|
|
cached
|
|
newtype TCfgNode =
|
|
TEntryNode(CfgScope scope) { succEntrySplits(scope, _, _, _) } or
|
|
TAnnotatedExitNode(CfgScope scope, boolean normal) {
|
|
exists(Reachability::SameSplitsBlock b, SuccessorType t | b.isReachable(_) |
|
|
succExitSplits(b.getANode(), _, scope, t) and
|
|
if isAbnormalExitType(t) then normal = false else normal = true
|
|
)
|
|
} or
|
|
TExitNode(CfgScope scope) {
|
|
exists(Reachability::SameSplitsBlock b | b.isReachable(_) |
|
|
succExitSplits(b.getANode(), _, scope, _)
|
|
)
|
|
} or
|
|
TAstNode(AstNode n, Splits splits) {
|
|
exists(Reachability::SameSplitsBlock b | b.isReachable(splits) | n = b.getANode())
|
|
}
|
|
|
|
cached
|
|
newtype TSuccessorType =
|
|
TSuccessorSuccessor() or
|
|
TBooleanSuccessor(boolean b) { b in [false, true] } or
|
|
TEmptinessSuccessor(boolean isEmpty) { isEmpty in [false, true] } or
|
|
TMatchingSuccessor(boolean isMatch) { isMatch in [false, true] } or
|
|
TReturnSuccessor() or
|
|
TBreakSuccessor() or
|
|
TNextSuccessor() or
|
|
TRedoSuccessor() or
|
|
TRetrySuccessor() or
|
|
TRaiseSuccessor() or // TODO: Add exception type?
|
|
TExitSuccessor()
|
|
|
|
/** Gets a successor node of a given flow type, if any. */
|
|
cached
|
|
CfgNode getASuccessor(CfgNode pred, SuccessorType t) {
|
|
exists(CfgScope scope, AstNode succElement, Splits succSplits |
|
|
pred = TEntryNode(scope) and
|
|
succEntrySplits(scope, succElement, succSplits, t) and
|
|
result = TAstNode(succElement, succSplits)
|
|
)
|
|
or
|
|
exists(AstNode predNode, Splits predSplits | pred = TAstNode(predNode, predSplits) |
|
|
exists(CfgScope scope, boolean normal |
|
|
succExitSplits(predNode, predSplits, scope, t) and
|
|
(if isAbnormalExitType(t) then normal = false else normal = true) and
|
|
result = TAnnotatedExitNode(scope, normal)
|
|
)
|
|
or
|
|
exists(AstNode succElement, Splits succSplits, Completion c |
|
|
succSplits(predNode, predSplits, succElement, succSplits, c) and
|
|
t = c.getAMatchingSuccessorType() and
|
|
result = TAstNode(succElement, succSplits)
|
|
)
|
|
)
|
|
or
|
|
exists(CfgScope scope |
|
|
pred = TAnnotatedExitNode(scope, _) and
|
|
t instanceof SuccessorTypes::NormalSuccessor and
|
|
result = TExitNode(scope)
|
|
)
|
|
}
|
|
|
|
/** Gets a first control flow element executed within `n`. */
|
|
cached
|
|
AstNode getAControlFlowEntryNode(AstNode n) { first(n, result) }
|
|
|
|
/** Gets a potential last control flow element executed within `n`. */
|
|
cached
|
|
AstNode getAControlFlowExitNode(AstNode n) { last(n, result, _) }
|
|
}
|
|
|
|
import Cached
|
|
|
|
/** An AST node that is split into multiple control flow nodes. */
|
|
class SplitAstNode extends AstNode {
|
|
SplitAstNode() { strictcount(CfgNode n | n.getNode() = this) > 1 }
|
|
}
|