mirror of
https://github.com/github/codeql.git
synced 2026-02-20 08:53:49 +01:00
553 lines
18 KiB
Plaintext
553 lines
18 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 AstNodes
|
|
private import codeql_ruby.controlflow.ControlFlowGraph
|
|
private import Completion
|
|
private import SuccessorTypes
|
|
private import Splitting
|
|
|
|
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() }
|
|
|
|
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 scope, AstNode first) {
|
|
exists(AstNode n |
|
|
first(scope, n) and
|
|
succImplIfHidden*(n, first) and
|
|
not isHidden(first)
|
|
)
|
|
}
|
|
|
|
/** Holds if `last` with completion `c` can exit `scope`. */
|
|
pragma[nomagic]
|
|
predicate succExit(AstNode last, CfgScope scope, Completion c) {
|
|
exists(AstNode n |
|
|
last(scope, 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. */
|
|
abstract AstNode getChildNode(int i);
|
|
|
|
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 |
|
|
last = max(int i | exists(this.getChildNodeRanked(i))) and
|
|
result = this.getChildNodeRanked(last)
|
|
)
|
|
}
|
|
|
|
/** Gets the `i`th child, which is not the last node. */
|
|
pragma[nomagic]
|
|
private AstNode getNonLastChildNode(int i) {
|
|
result = this.getChildNodeRanked(i) and
|
|
not result = this.getLastChildNode()
|
|
}
|
|
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getChildNode(_) }
|
|
|
|
pragma[nomagic]
|
|
override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
exists(int i |
|
|
last(this.getNonLastChildNode(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)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
final override predicate propagatesAbnormal(AstNode child) { none() }
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
|
|
}
|
|
|
|
/** Defines the CFG by dispatch on the various AST types. */
|
|
private module Trees {
|
|
private class ArgumentListTree extends StandardPostOrderTree, ArgumentList {
|
|
final override AstNode getChildNode(int i) { result = this.getChild(i) }
|
|
|
|
override predicate isHidden() { any() }
|
|
}
|
|
|
|
private class ArrayTree extends StandardPostOrderTree, Array {
|
|
final override AstNode getChildNode(int i) { result = this.getChild(i) }
|
|
}
|
|
|
|
private class AssignmentTree extends StandardPostOrderTree, Assignment {
|
|
final override AstNode getChildNode(int i) {
|
|
result = this.getLeft() and i = 0
|
|
or
|
|
result = this.getRight() and i = 1
|
|
}
|
|
}
|
|
|
|
private class BinaryTree extends StandardPostOrderTree, Binary {
|
|
BinaryTree() { not this instanceof LogicalAndAstNode and not this instanceof LogicalOrAstNode }
|
|
|
|
final override AstNode getChildNode(int i) {
|
|
result = this.getLeft() and i = 0
|
|
or
|
|
result = this.getRight() and i = 1
|
|
}
|
|
}
|
|
|
|
private class BlockParametersTree extends LeafTree, BlockParameters {
|
|
override predicate isHidden() { any() }
|
|
}
|
|
|
|
private class BreakTree extends StandardPostOrderTree, Break {
|
|
final override AstNode getChildNode(int i) { result = this.getChild() and i = 0 }
|
|
}
|
|
|
|
private class CallTree extends StandardPostOrderTree, Call {
|
|
final override AstNode getChildNode(int i) {
|
|
result = this.getReceiver() and i = 0
|
|
or
|
|
result = this.getMethod() and i = 1
|
|
}
|
|
}
|
|
|
|
private class DoTree extends StandardPreOrderTree, Do {
|
|
final override AstNode getChildNode(int i) { result = this.getChild(i) }
|
|
|
|
override predicate isHidden() { any() }
|
|
}
|
|
|
|
private class DoBlockTree extends StandardPreOrderTree, DoBlock {
|
|
final override AstNode getChildNode(int i) { result = this.getChild(i) }
|
|
|
|
override predicate isHidden() { any() }
|
|
}
|
|
|
|
private class ElseTree extends StandardPreOrderTree, Else {
|
|
final override AstNode getChildNode(int i) { result = this.getChild(i) }
|
|
|
|
override predicate isHidden() { any() }
|
|
}
|
|
|
|
private class IdentifierTree extends LeafTree, Identifier { }
|
|
|
|
private class IfElsifTree extends PreOrderTree, IfElsifAstNode {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getConditionNode() }
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getConditionNode(), last, c) and
|
|
c instanceof FalseCompletion and
|
|
not exists(this.getAlternativeNode())
|
|
or
|
|
last(this.getConsequenceNode(), last, c)
|
|
or
|
|
last(this.getAlternativeNode(), last, c)
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
pred = this and
|
|
first(this.getConditionNode(), succ) and
|
|
c instanceof SimpleCompletion
|
|
or
|
|
last(this.getConditionNode(), pred, c) and
|
|
(
|
|
c instanceof TrueCompletion and first(this.getConsequenceNode(), succ)
|
|
or
|
|
c instanceof FalseCompletion and first(this.getAlternativeNode(), succ)
|
|
)
|
|
}
|
|
}
|
|
|
|
private class IntegerTree extends LeafTree, Integer { }
|
|
|
|
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.(BooleanCompletion).getDual())
|
|
or
|
|
last(operand, pred, c) and
|
|
c instanceof SimpleCompletion
|
|
)
|
|
}
|
|
}
|
|
|
|
private class MethodTree extends StandardPreOrderTree, Method {
|
|
final override AstNode getChildNode(int i) { result = this.getChild(i) }
|
|
|
|
override predicate isHidden() { any() }
|
|
}
|
|
|
|
private class MethodCallTree extends StandardPostOrderTree, MethodCall {
|
|
// this.getBlock() is not included as it uses a different scope
|
|
final override AstNode getChildNode(int i) {
|
|
result = this.getArguments() and i = 0
|
|
or
|
|
result = this.getMethod() and i = 1
|
|
}
|
|
}
|
|
|
|
private class NextTree extends StandardPostOrderTree, Next {
|
|
final override AstNode getChildNode(int i) { result = this.getChild() and i = 0 }
|
|
}
|
|
|
|
private class OperatorAssignmentTree extends StandardPostOrderTree, OperatorAssignment {
|
|
final override AstNode getChildNode(int i) {
|
|
result = this.getLeft() and i = 0
|
|
or
|
|
result = this.getRight() and i = 1
|
|
}
|
|
}
|
|
|
|
private class ParenthesizedStatementsTree extends StandardPostOrderTree, ParenthesizedStatements {
|
|
final override AstNode getChildNode(int i) { result = this.getChild(i) }
|
|
}
|
|
|
|
private class RedoTree extends StandardPostOrderTree, Redo {
|
|
final override AstNode getChildNode(int i) { result = this.getChild() and i = 0 }
|
|
}
|
|
|
|
private class ReturnTree extends StandardPostOrderTree, Return {
|
|
final override AstNode getChildNode(int i) { result = this.getChild() and i = 0 }
|
|
}
|
|
|
|
private class StringTree extends LeafTree, String { }
|
|
|
|
private class ThenTree extends StandardPreOrderTree, Then {
|
|
final override AstNode getChildNode(int i) { result = this.getChild(i) }
|
|
|
|
override predicate isHidden() { any() }
|
|
}
|
|
|
|
private class UnaryTree extends StandardPostOrderTree, Unary {
|
|
UnaryTree() { not this instanceof LogicalNotAstNode }
|
|
|
|
final override AstNode getChildNode(int i) { result = this.getOperand() and i = 0 }
|
|
}
|
|
|
|
private class WhileTree extends PreOrderTree, While {
|
|
final override predicate propagatesAbnormal(AstNode child) { child = this.getCondition() }
|
|
|
|
final override predicate last(AstNode last, Completion c) {
|
|
last(this.getCondition(), last, c) and
|
|
c instanceof FalseCompletion
|
|
or
|
|
last(this.getBody(), last, c) and
|
|
not c.continuesLoop() and
|
|
not c instanceof BreakCompletion and
|
|
not c instanceof RedoCompletion
|
|
or
|
|
c =
|
|
any(NestedCompletion nc |
|
|
last(this.getBody(), last, nc.getInnerCompletion().(BreakCompletion)) and
|
|
nc.getOuterCompletion() instanceof SimpleCompletion
|
|
)
|
|
}
|
|
|
|
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
|
pred = this and
|
|
first(this.getCondition(), succ) and
|
|
c instanceof SimpleCompletion
|
|
or
|
|
last(this.getCondition(), pred, c) and
|
|
c instanceof TrueCompletion and
|
|
first(this.getBody(), succ)
|
|
or
|
|
last(this.getBody(), pred, c) and
|
|
first(this.getCondition(), succ) and
|
|
c.continuesLoop()
|
|
or
|
|
last(this.getBody(), pred, any(RedoCompletion rc)) and
|
|
first(this.getBody(), succ) and
|
|
c instanceof SimpleCompletion
|
|
}
|
|
}
|
|
}
|
|
|
|
cached
|
|
private module Cached {
|
|
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 = true or b = false } 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 }
|
|
}
|