Files
codeql/ql/src/codeql_ruby/controlflow/internal/ControlFlowGraphImpl.qll
2020-11-26 13:45:45 +00:00

591 lines
19 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.Generated::Generated
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) and
// Never descend into children with a separate scope
not child instanceof CfgScope
|
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))
)
}
final override predicate propagatesAbnormal(AstNode child) { child = this.getChildNodeRanked(_) }
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)
}
}
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 BeginBlockTree extends StandardPreOrderTree, BeginBlock {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
}
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 ClassTree extends StandardPreOrderTree, Class {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
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 EndBlockTree extends StandardPreOrderTree, EndBlock {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
}
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 ModuleTree extends StandardPreOrderTree, Module {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
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 ProgramTree extends StandardPreOrderTree, Program {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
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 SingletonClassTree extends StandardPreOrderTree, SingletonClass {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
private class SingletonMethodTree extends StandardPreOrderTree, SingletonMethod {
final override AstNode getChildNode(int i) { result = this.getChild(i) }
override predicate isHidden() { any() }
}
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 }
}