mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
1165 lines
38 KiB
Plaintext
1165 lines
38 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
|
|
private import codeql.ruby.ast.internal.AST as ASTInternal
|
|
private import codeql.ruby.ast.internal.Scope
|
|
private import codeql.ruby.ast.Scope
|
|
private import codeql.ruby.ast.internal.TreeSitter
|
|
private import codeql.ruby.ast.internal.Variable
|
|
private import codeql.ruby.controlflow.ControlFlowGraph
|
|
private import Completion
|
|
import ControlFlowGraphImplShared
|
|
|
|
module CfgScope {
|
|
abstract class Range_ extends AstNode {
|
|
abstract predicate entry(AstNode first);
|
|
|
|
abstract predicate exit(AstNode last, Completion c);
|
|
}
|
|
|
|
private class ToplevelScope extends Range_, Toplevel {
|
|
final override predicate entry(AstNode first) { first(this, first) }
|
|
|
|
final override predicate exit(AstNode last, Completion c) { last(this, last, c) }
|
|
}
|
|
|
|
private class EndBlockScope extends Range_, EndBlock {
|
|
final override predicate entry(AstNode first) {
|
|
first(this.(Trees::EndBlockTree).getBodyChild(0, _), first)
|
|
}
|
|
|
|
final override predicate exit(AstNode last, Completion c) {
|
|
last(this.(Trees::EndBlockTree).getLastBodyChild(), last, c)
|
|
}
|
|
}
|
|
|
|
private class BodyStmtCallableScope extends Range_, ASTInternal::TBodyStmt, Callable {
|
|
final override predicate entry(AstNode first) { this.(Trees::BodyStmtTree).firstInner(first) }
|
|
|
|
final override predicate exit(AstNode last, Completion c) {
|
|
this.(Trees::BodyStmtTree).lastInner(last, c)
|
|
}
|
|
}
|
|
|
|
private class BraceBlockScope extends Range_, BraceBlock {
|
|
final override predicate entry(AstNode first) {
|
|
first(this.(Trees::BraceBlockTree).getBodyChild(0, _), first)
|
|
}
|
|
|
|
final override predicate exit(AstNode last, Completion c) {
|
|
last(this.(Trees::BraceBlockTree).getLastBodyChild(), last, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Holds if `first` is first executed when entering `scope`. */
|
|
pragma[nomagic]
|
|
predicate succEntry(CfgScope::Range_ scope, AstNode first) { scope.entry(first) }
|
|
|
|
/** Holds if `last` with completion `c` can exit `scope`. */
|
|
pragma[nomagic]
|
|
predicate succExit(CfgScope::Range_ scope, AstNode last, Completion c) { scope.exit(last, c) }
|
|
|
|
// TODO: remove this class; it should be replaced with an implicit non AST node
|
|
private class ForIn extends AstNode, ASTInternal::TForIn {
|
|
final override string toString() { result = "In" }
|
|
}
|
|
|
|
// TODO: remove this class; it should be replaced with an implicit non AST node
|
|
private class ForRange extends ForExpr {
|
|
override AstNode getAChild(string pred) {
|
|
result = super.getAChild(pred)
|
|
or
|
|
pred = "<in>" and
|
|
result = this.getIn()
|
|
}
|
|
|
|
ForIn getIn() {
|
|
result = ASTInternal::TForIn(ASTInternal::toGenerated(this).(Ruby::For).getValue())
|
|
}
|
|
}
|
|
|
|
/** Defines the CFG by dispatch on the various AST types. */
|
|
module Trees {
|
|
private class AliasStmtTree extends StandardPreOrderTree, AliasStmt {
|
|
final override ControlFlowTree getChildElement(int i) {
|
|
result = this.getNewName() and i = 0
|
|
or
|
|
result = this.getOldName() and i = 1
|
|
}
|
|
}
|
|
|
|
private class ArgumentListTree extends StandardTree, ArgumentList {
|
|
final override ControlFlowTree getChildElement(int i) { result = this.getElement(i) }
|
|
|
|
final override predicate first(AstNode first) { first(this.getFirstChildElement(), first) }
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getLastChildElement(), last, c)
|
|
}
|
|
}
|
|
|
|
private class AssignExprTree extends StandardPostOrderTree, AssignExpr {
|
|
AssignExprTree() {
|
|
exists(Expr left | left = this.getLeftOperand() |
|
|
left instanceof VariableAccess or
|
|
left instanceof ConstantAccess
|
|
)
|
|
}
|
|
|
|
final override ControlFlowTree getChildElement(int i) {
|
|
result = this.getLeftOperand() and i = 0
|
|
or
|
|
result = this.getRightOperand() and i = 1
|
|
}
|
|
}
|
|
|
|
private class BeginTree extends BodyStmtTree, BeginExpr {
|
|
final override predicate first(AstNode first) { this.firstInner(first) }
|
|
|
|
final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) }
|
|
|
|
final override predicate propagatesAbnormal(AstNode child) { none() }
|
|
}
|
|
|
|
private class BlockArgumentTree extends StandardPostOrderTree, BlockArgument {
|
|
final override ControlFlowTree getChildElement(int i) { result = this.getValue() and i = 0 }
|
|
}
|
|
|
|
abstract private class NonDefaultValueParameterTree extends ControlFlowTree, NamedParameter {
|
|
final override predicate first(AstNode first) {
|
|
this.getDefiningAccess().(ControlFlowTree).first(first)
|
|
}
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
this.getDefiningAccess().(ControlFlowTree).last(last, c)
|
|
}
|
|
|
|
override predicate propagatesAbnormal(AstNode child) {
|
|
this.getDefiningAccess().(ControlFlowTree).propagatesAbnormal(child)
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
|
|
}
|
|
|
|
private class BlockParameterTree extends NonDefaultValueParameterTree, BlockParameter { }
|
|
|
|
abstract class BodyStmtTree extends StmtSequenceTree, BodyStmt {
|
|
override predicate first(AstNode first) { first = this }
|
|
|
|
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)
|
|
)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
// 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)
|
|
}
|
|
|
|
/**
|
|
* 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) }
|
|
|
|
/**
|
|
* Gets a descendant that belongs to the `ensure` block of this block, if any.
|
|
* Nested `ensure` blocks are not included.
|
|
*/
|
|
pragma[nomagic]
|
|
AstNode getAnEnsureDescendant() {
|
|
result = this.getEnsure()
|
|
or
|
|
exists(AstNode mid |
|
|
mid = this.getAnEnsureDescendant() and
|
|
result = mid.getAChild() and
|
|
getCfgScope(result) = getCfgScope(mid) and
|
|
not exists(BodyStmt 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(BodyStmtTree innerBlock) {
|
|
exists(StmtSequence innerEnsure |
|
|
innerEnsure = this.getAnEnsureDescendant().getAChild() and
|
|
getCfgScope(innerEnsure) = getCfgScope(this) and
|
|
innerEnsure = innerBlock.(BodyStmt).getEnsure()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the `ensure`-nesting level of this block. That is, the number of `ensure`
|
|
* blocks that this block is nested under.
|
|
*/
|
|
int getNestLevel() { result = count(BodyStmtTree outer | outer.nestedEnsure+(this)) }
|
|
|
|
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.getNestLevel()
|
|
}
|
|
|
|
/**
|
|
* 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, _))
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
private class BooleanLiteralTree extends LeafTree, BooleanLiteral { }
|
|
|
|
class BraceBlockTree extends StmtSequenceTree, BraceBlock {
|
|
final override predicate propagatesAbnormal(AstNode child) { none() }
|
|
|
|
final override AstNode getBodyChild(int i, boolean rescuable) {
|
|
result = this.getParameter(i) and rescuable = false
|
|
or
|
|
result = StmtSequenceTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable)
|
|
}
|
|
|
|
override predicate first(AstNode first) { first = this }
|
|
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
// 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
|
|
)
|
|
}
|
|
}
|
|
|
|
private class CallTree extends StandardPostOrderTree, Call {
|
|
CallTree() {
|
|
// Logical operations are handled separately
|
|
not this instanceof UnaryLogicalOperation and
|
|
not this instanceof BinaryLogicalOperation
|
|
}
|
|
|
|
override ControlFlowTree getChildElement(int i) { result = this.getArgument(i) }
|
|
}
|
|
|
|
private class CaseTree extends PreOrderTree, CaseExpr {
|
|
final override predicate propagatesAbnormal(AstNode child) {
|
|
child = this.getValue() or child = this.getABranch()
|
|
}
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getValue(), last, c) and not exists(this.getABranch())
|
|
or
|
|
last(this.getAWhenBranch().getBody(), last, c)
|
|
or
|
|
exists(int i, ControlFlowTree lastBranch |
|
|
lastBranch = this.getBranch(i) and
|
|
not exists(this.getBranch(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.getBranch(0)
|
|
)
|
|
or
|
|
last(this.getValue(), pred, c) and
|
|
first(this.getBranch(0), succ) and
|
|
c instanceof SimpleCompletion
|
|
or
|
|
exists(int i, WhenTree branch | branch = this.getBranch(i) |
|
|
last(branch.getLastPattern(), pred, c) and
|
|
first(this.getBranch(i + 1), succ) and
|
|
c.(ConditionalCompletion).getValue() = false
|
|
)
|
|
}
|
|
}
|
|
|
|
private class CharacterTree extends LeafTree, CharacterLiteral { }
|
|
|
|
private class ClassDeclarationTree extends NamespaceTree, ClassDeclaration {
|
|
/** Gets the `i`th child in the body of this block. */
|
|
final override AstNode getBodyChild(int i, boolean rescuable) {
|
|
result = this.getScopeExpr() and i = 0 and rescuable = false
|
|
or
|
|
result = this.getSuperclassExpr() and
|
|
i = count(this.getScopeExpr()) and
|
|
rescuable = true
|
|
or
|
|
result =
|
|
super
|
|
.getBodyChild(i - count(this.getScopeExpr()) - count(this.getSuperclassExpr()),
|
|
rescuable)
|
|
}
|
|
}
|
|
|
|
private class ClassVariableTree extends LeafTree, ClassVariableAccess { }
|
|
|
|
private class ConditionalExprTree extends PostOrderTree, ConditionalExpr {
|
|
final override predicate propagatesAbnormal(AstNode child) {
|
|
child = this.getCondition() or child = this.getBranch(_)
|
|
}
|
|
|
|
final override predicate first(AstNode first) { first(this.getCondition(), first) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
exists(boolean b |
|
|
last(this.getCondition(), 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 ConditionalLoopTree extends PostOrderTree, ConditionalLoop {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getCondition() }
|
|
|
|
final override predicate first(AstNode first) { first(this.getCondition(), first) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
last(this.getCondition(), pred, c) and
|
|
this.entersLoopWhenConditionIs(c.(BooleanCompletion).getValue()) and
|
|
first(this.getBody(), succ)
|
|
or
|
|
last(this.getBody(), pred, c) and
|
|
first(this.getCondition(), 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.getCondition(), pred, c) and
|
|
this.entersLoopWhenConditionIs(c.(BooleanCompletion).getValue().booleanNot())
|
|
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 ConstantAccessTree extends PostOrderTree, ConstantAccess {
|
|
ConstantAccessTree() {
|
|
not this instanceof ClassDeclaration and
|
|
not this instanceof ModuleDeclaration
|
|
}
|
|
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getScopeExpr() }
|
|
|
|
final override predicate first(AstNode first) {
|
|
first(this.getScopeExpr(), first)
|
|
or
|
|
not exists(this.getScopeExpr()) and
|
|
first = this
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
last(this.getScopeExpr(), pred, c) and
|
|
succ = this and
|
|
c instanceof NormalCompletion
|
|
}
|
|
}
|
|
|
|
/** A parameter that may have a default value. */
|
|
abstract class DefaultValueParameterTree extends ControlFlowTree {
|
|
abstract Expr getDefaultValueExpr();
|
|
|
|
abstract AstNode getAccessNode();
|
|
|
|
predicate hasDefaultValue() { exists(this.getDefaultValueExpr()) }
|
|
|
|
final override predicate propagatesAbnormal(AstNode child) {
|
|
child = this.getDefaultValueExpr() or child = this.getAccessNode()
|
|
}
|
|
|
|
final override predicate first(AstNode first) { first = this.getAccessNode() }
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getDefaultValueExpr(), last, c) and
|
|
c instanceof NormalCompletion
|
|
or
|
|
last = this.getAccessNode() 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.getAccessNode() and
|
|
first(this.getDefaultValueExpr(), succ) and
|
|
c.(MatchingCompletion).getValue() = false
|
|
}
|
|
}
|
|
|
|
private class DesugaredTree extends ControlFlowTree {
|
|
ControlFlowTree desugared;
|
|
|
|
DesugaredTree() { desugared = this.getDesugared() }
|
|
|
|
final override predicate propagatesAbnormal(AstNode child) {
|
|
desugared.propagatesAbnormal(child)
|
|
}
|
|
|
|
final override predicate first(AstNode first) { desugared.first(first) }
|
|
|
|
final override predicate last(AstNode last, Completion c) { desugared.last(last, c) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
|
|
}
|
|
|
|
private class DoBlockTree extends BodyStmtTree, DoBlock {
|
|
/** Gets the `i`th child in the body of this block. */
|
|
final override AstNode getBodyChild(int i, boolean rescuable) {
|
|
result = this.getParameter(i) and rescuable = false
|
|
or
|
|
result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable)
|
|
}
|
|
|
|
override predicate propagatesAbnormal(AstNode child) { none() }
|
|
}
|
|
|
|
private class EmptyStatementTree extends LeafTree, EmptyStmt { }
|
|
|
|
class EndBlockTree extends StmtSequenceTree, EndBlock {
|
|
override predicate first(AstNode first) { first = this }
|
|
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
// 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
|
|
)
|
|
}
|
|
}
|
|
|
|
private class ForInTree extends LeafTree, ForIn { }
|
|
|
|
/**
|
|
* 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, ForRange {
|
|
final override predicate propagatesAbnormal(AstNode child) {
|
|
child = this.getPattern() or child = this.getValue()
|
|
}
|
|
|
|
final override predicate first(AstNode first) { first(this.getValue(), first) }
|
|
|
|
/**
|
|
* 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.getValue(), 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, GlobalVariableAccess { }
|
|
|
|
private class HashLiteralTree extends StandardPostOrderTree, HashLiteral {
|
|
final override ControlFlowTree getChildElement(int i) { result = this.getElement(i) }
|
|
}
|
|
|
|
private class HashSplatParameterTree extends NonDefaultValueParameterTree, HashSplatParameter { }
|
|
|
|
private class HereDocTree extends StandardPreOrderTree, HereDoc {
|
|
final override ControlFlowTree getChildElement(int i) { result = this.getComponent(i) }
|
|
}
|
|
|
|
private class InstanceVariableTree extends LeafTree, InstanceVariableAccess { }
|
|
|
|
private class KeywordParameterTree extends DefaultValueParameterTree, KeywordParameter {
|
|
final override Expr getDefaultValueExpr() { result = this.getDefaultValue() }
|
|
|
|
final override AstNode getAccessNode() { result = this.getDefiningAccess() }
|
|
}
|
|
|
|
private class LambdaTree extends BodyStmtTree, Lambda {
|
|
final override predicate propagatesAbnormal(AstNode child) { none() }
|
|
|
|
/** Gets the `i`th child in the body of this block. */
|
|
final override AstNode getBodyChild(int i, boolean rescuable) {
|
|
result = this.getParameter(i) and rescuable = false
|
|
or
|
|
result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable)
|
|
}
|
|
}
|
|
|
|
private class LocalVariableAccessTree extends LeafTree, LocalVariableAccess { }
|
|
|
|
private class LogicalAndTree extends PostOrderTree, LogicalAndExpr {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getAnOperand() }
|
|
|
|
final override predicate first(AstNode first) { first(this.getLeftOperand(), first) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
last(this.getLeftOperand(), pred, c) and
|
|
c instanceof TrueCompletion and
|
|
first(this.getRightOperand(), succ)
|
|
or
|
|
last(this.getLeftOperand(), pred, c) and
|
|
c instanceof FalseCompletion and
|
|
succ = this
|
|
or
|
|
last(this.getRightOperand(), pred, c) and
|
|
c instanceof NormalCompletion and
|
|
succ = this
|
|
}
|
|
}
|
|
|
|
private class LogicalNotTree extends PostOrderTree, NotExpr {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getOperand() }
|
|
|
|
final override predicate first(AstNode first) { first(this.getOperand(), first) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
succ = this and
|
|
last(this.getOperand(), pred, c) and
|
|
c instanceof NormalCompletion
|
|
}
|
|
}
|
|
|
|
private class LogicalOrTree extends PostOrderTree, LogicalOrExpr {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getAnOperand() }
|
|
|
|
final override predicate first(AstNode first) { first(this.getLeftOperand(), first) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
last(this.getLeftOperand(), pred, c) and
|
|
c instanceof FalseCompletion and
|
|
first(this.getRightOperand(), succ)
|
|
or
|
|
last(this.getLeftOperand(), pred, c) and
|
|
c instanceof TrueCompletion and
|
|
succ = this
|
|
or
|
|
last(this.getRightOperand(), pred, c) and
|
|
c instanceof NormalCompletion and
|
|
succ = this
|
|
}
|
|
}
|
|
|
|
private class MethodCallTree extends CallTree, MethodCall {
|
|
final override ControlFlowTree getChildElement(int i) {
|
|
result = this.getReceiver() and i = 0
|
|
or
|
|
result = this.getArgument(i - 1)
|
|
or
|
|
result = this.getBlock() and i = 1 + this.getNumberOfArguments()
|
|
}
|
|
}
|
|
|
|
private class MethodNameTree extends LeafTree, MethodName, ASTInternal::TTokenMethodName { }
|
|
|
|
private class MethodTree extends BodyStmtTree, Method {
|
|
final override predicate propagatesAbnormal(AstNode child) { none() }
|
|
|
|
/** Gets the `i`th child in the body of this block. */
|
|
final override AstNode getBodyChild(int i, boolean rescuable) {
|
|
result = this.getParameter(i) and rescuable = false
|
|
or
|
|
result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable)
|
|
}
|
|
}
|
|
|
|
private class ModuleDeclarationTree extends NamespaceTree, ModuleDeclaration {
|
|
/** Gets the `i`th child in the body of this block. */
|
|
final override AstNode getBodyChild(int i, boolean rescuable) {
|
|
result = this.getScopeExpr() and i = 0 and rescuable = false
|
|
or
|
|
result = NamespaceTree.super.getBodyChild(i - count(this.getScopeExpr()), rescuable)
|
|
}
|
|
}
|
|
|
|
private class NamespaceTree extends BodyStmtTree, Namespace {
|
|
final override predicate first(AstNode first) {
|
|
this.firstInner(first)
|
|
or
|
|
not exists(this.getAChild(_)) and
|
|
first = this
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
BodyStmtTree.super.succ(pred, succ, c)
|
|
or
|
|
succ = this and
|
|
this.lastInner(pred, c)
|
|
}
|
|
}
|
|
|
|
private class NilTree extends LeafTree, NilLiteral { }
|
|
|
|
private class NumericLiteralTree extends LeafTree, NumericLiteral { }
|
|
|
|
private class OptionalParameterTree extends DefaultValueParameterTree, OptionalParameter {
|
|
final override Expr getDefaultValueExpr() { result = this.getDefaultValue() }
|
|
|
|
final override AstNode getAccessNode() { result = this.getDefiningAccess() }
|
|
}
|
|
|
|
private class PairTree extends StandardPostOrderTree, Pair {
|
|
final override ControlFlowTree getChildElement(int i) {
|
|
result = this.getKey() and i = 0
|
|
or
|
|
result = this.getValue() and i = 1
|
|
}
|
|
}
|
|
|
|
private class RangeLiteralTree extends StandardPostOrderTree, RangeLiteral {
|
|
final override ControlFlowTree getChildElement(int i) {
|
|
result = this.getBegin() and i = 0
|
|
or
|
|
result = this.getEnd() and i = 1
|
|
}
|
|
}
|
|
|
|
private class RedoStmtTree extends LeafTree, RedoStmt { }
|
|
|
|
private class RescueModifierTree extends PreOrderTree, RescueModifierExpr {
|
|
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 RescueTree extends PreOrderTree, RescueClause {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getAnException() }
|
|
|
|
private Expr getLastException() {
|
|
exists(int i | result = this.getException(i) and not exists(this.getException(i + 1)))
|
|
}
|
|
|
|
predicate lastMatch(AstNode last, Completion c) {
|
|
last(this.getBody(), last, c)
|
|
or
|
|
not exists(this.getBody()) and
|
|
(
|
|
last(this.getVariableExpr(), last, c)
|
|
or
|
|
not exists(this.getVariableExpr()) and
|
|
(
|
|
last(this.getAnException(), last, c) and
|
|
c.(MatchingCompletion).getValue() = true
|
|
or
|
|
not exists(this.getAnException()) and
|
|
last = this and
|
|
c.isValidFor(this)
|
|
)
|
|
)
|
|
}
|
|
|
|
predicate lastNoMatch(AstNode last, Completion c) {
|
|
last(this.getLastException(), 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.getException(0)
|
|
or
|
|
not exists(this.getException(0)) and
|
|
(
|
|
next = this.getVariableExpr()
|
|
or
|
|
not exists(this.getVariableExpr()) and
|
|
next = this.getBody()
|
|
)
|
|
)
|
|
or
|
|
exists(AstNode next |
|
|
last(this.getAnException(), pred, c) and
|
|
first(next, succ) and
|
|
c.(MatchingCompletion).getValue() = true
|
|
|
|
|
next = this.getVariableExpr()
|
|
or
|
|
not exists(this.getVariableExpr()) and
|
|
next = this.getBody()
|
|
)
|
|
or
|
|
exists(int i |
|
|
last(this.getException(i), pred, c) and
|
|
c.(MatchingCompletion).getValue() = false and
|
|
first(this.getException(i + 1), succ)
|
|
)
|
|
or
|
|
last(this.getVariableExpr(), pred, c) and
|
|
first(this.getBody(), succ) and
|
|
c instanceof NormalCompletion
|
|
}
|
|
}
|
|
|
|
private class RetryStmtTree extends LeafTree, RetryStmt { }
|
|
|
|
private class ReturningStmtTree extends StandardPostOrderTree, ReturningStmt {
|
|
final override ControlFlowTree getChildElement(int i) { result = this.getValue() and i = 0 }
|
|
}
|
|
|
|
private class SelfTree extends LeafTree, Self { }
|
|
|
|
private class SimpleParameterTree extends NonDefaultValueParameterTree, SimpleParameter { }
|
|
|
|
// Corner case: For duplicated '_' parameters, only the first occurence has a defining
|
|
// access. For subsequent parameters we simply include the parameter itself in the CFG
|
|
private class SimpleParameterTreeDupUnderscore extends LeafTree, SimpleParameter {
|
|
SimpleParameterTreeDupUnderscore() { not exists(this.getDefiningAccess()) }
|
|
}
|
|
|
|
private class SingletonClassTree extends BodyStmtTree, SingletonClass {
|
|
final override predicate first(AstNode first) {
|
|
this.firstInner(first)
|
|
or
|
|
not exists(this.getAChild(_)) and
|
|
first = this
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
BodyStmtTree.super.succ(pred, succ, c)
|
|
or
|
|
succ = this and
|
|
this.lastInner(pred, c)
|
|
}
|
|
|
|
/** Gets the `i`th child in the body of this block. */
|
|
final override AstNode getBodyChild(int i, boolean rescuable) {
|
|
(
|
|
result = this.getValue() and i = 0 and rescuable = false
|
|
or
|
|
result = BodyStmtTree.super.getBodyChild(i - 1, rescuable)
|
|
)
|
|
}
|
|
}
|
|
|
|
private class SingletonMethodTree extends BodyStmtTree, SingletonMethod {
|
|
final override predicate propagatesAbnormal(AstNode child) { none() }
|
|
|
|
/** Gets the `i`th child in the body of this block. */
|
|
final override AstNode getBodyChild(int i, boolean rescuable) {
|
|
result = this.getParameter(i) and rescuable = false
|
|
or
|
|
result = BodyStmtTree.super.getBodyChild(i - this.getNumberOfParameters(), rescuable)
|
|
}
|
|
|
|
override predicate first(AstNode first) { first(this.getObject(), first) }
|
|
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
BodyStmtTree.super.succ(pred, succ, c)
|
|
or
|
|
last(this.getObject(), pred, c) and
|
|
succ = this and
|
|
c instanceof NormalCompletion
|
|
}
|
|
}
|
|
|
|
private class SplatParameterTree extends NonDefaultValueParameterTree, SplatParameter { }
|
|
|
|
class StmtSequenceTree extends PostOrderTree, StmtSequence {
|
|
override predicate propagatesAbnormal(AstNode child) { child = this.getAStmt() }
|
|
|
|
override predicate first(AstNode first) { first(this.getStmt(0), first) }
|
|
|
|
/** Gets the `i`th child in the body of this body statement. */
|
|
AstNode getBodyChild(int i, boolean rescuable) {
|
|
result = this.getStmt(i) and
|
|
rescuable = true
|
|
}
|
|
|
|
final AstNode getLastBodyChild() {
|
|
exists(int i |
|
|
result = this.getBodyChild(i, _) and
|
|
not exists(this.getBodyChild(i + 1, _))
|
|
)
|
|
}
|
|
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
// 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
|
|
succ = this and
|
|
last(this.getLastBodyChild(), pred, c) and
|
|
c instanceof NormalCompletion
|
|
}
|
|
}
|
|
|
|
private class StringConcatenationTree extends StandardTree, StringConcatenation {
|
|
final override ControlFlowTree getChildElement(int i) { result = this.getString(i) }
|
|
|
|
final override predicate first(AstNode first) { first(this.getFirstChildElement(), first) }
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getLastChildElement(), last, c)
|
|
}
|
|
}
|
|
|
|
private class StringlikeLiteralTree extends StandardPostOrderTree, StringlikeLiteral {
|
|
StringlikeLiteralTree() { not this instanceof HereDoc }
|
|
|
|
final override ControlFlowTree getChildElement(int i) { result = this.getComponent(i) }
|
|
}
|
|
|
|
private class ToplevelTree extends BodyStmtTree, Toplevel {
|
|
final override AstNode getBodyChild(int i, boolean rescuable) {
|
|
result = this.getBeginBlock(i) and rescuable = true
|
|
or
|
|
result = BodyStmtTree.super.getBodyChild(i - count(this.getABeginBlock()), rescuable)
|
|
}
|
|
|
|
final override predicate first(AstNode first) { this.firstInner(first) }
|
|
|
|
final override predicate last(AstNode last, Completion c) { this.lastInner(last, c) }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
BodyStmtTree.super.succ(pred, succ, c)
|
|
}
|
|
}
|
|
|
|
private class TuplePatternTree extends StandardPostOrderTree, TuplePattern {
|
|
final override ControlFlowTree getChildElement(int i) { result = this.getElement(i) }
|
|
}
|
|
|
|
private class UndefStmtTree extends StandardPreOrderTree, UndefStmt {
|
|
final override ControlFlowTree getChildElement(int i) { result = this.getMethodName(i) }
|
|
}
|
|
|
|
private class WhenTree extends PreOrderTree, WhenExpr {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getAPattern() }
|
|
|
|
final Expr 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, Expr 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 Scope parent(Scope n) {
|
|
result = n.getOuterScope() and
|
|
not n instanceof CfgScope::Range_
|
|
}
|
|
|
|
/** Gets the CFG scope of node `n`. */
|
|
pragma[inline]
|
|
CfgScope getCfgScope(AstNode n) {
|
|
exists(AstNode n0 |
|
|
pragma[only_bind_into](n0) = n and
|
|
pragma[only_bind_into](result) = getCfgScopeImpl(n0)
|
|
)
|
|
}
|
|
|
|
cached
|
|
private module Cached {
|
|
/** Gets the CFG scope of node `n`. */
|
|
cached
|
|
CfgScope getCfgScopeImpl(AstNode n) {
|
|
forceCachingInSameStage() and
|
|
result = parent*(ASTInternal::fromGenerated(scopeOf(ASTInternal::toGeneratedInclSynth(n))))
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
import Cached
|