mirror of
https://github.com/github/codeql.git
synced 2026-02-20 08:53:49 +01:00
Initial CFG skeleton code
This commit is contained in:
309
ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll
Normal file
309
ql/src/codeql_ruby/controlflow/ControlFlowGraph.qll
Normal file
@@ -0,0 +1,309 @@
|
||||
/** Provides classes representing the control flow graph. */
|
||||
|
||||
import codeql_ruby.ast
|
||||
private import codeql_ruby.controlflow.BasicBlocks
|
||||
private import SuccessorTypes
|
||||
private import internal.ControlFlowGraphImpl
|
||||
private import internal.Splitting
|
||||
private import internal.Completion
|
||||
|
||||
/** An AST node with an associated control-flow graph. */
|
||||
class CfgScope = Method; // TODO: add more cases (e.g. bodies of lambdas/do blocks)
|
||||
|
||||
/**
|
||||
* A control flow node.
|
||||
*
|
||||
* A control flow node is a node in the control flow graph (CFG). There is a
|
||||
* many-to-one relationship between CFG nodes and AST nodes.
|
||||
*
|
||||
* Only nodes that can be reached from an entry point are included in the CFG.
|
||||
*/
|
||||
class CfgNode extends TCfgNode {
|
||||
/** Gets a textual representation of this control flow node. */
|
||||
string toString() { none() }
|
||||
|
||||
/** Gets the AST node that this node corresponds to, if any. */
|
||||
AstNode getNode() { none() }
|
||||
|
||||
/** Gets the location of this control flow node. */
|
||||
Location getLocation() { result = this.getNode().getLocation() }
|
||||
|
||||
/** Holds if this control flow node has conditional successors. */
|
||||
final predicate isCondition() { exists(this.getASuccessor(any(BooleanSuccessor bs))) }
|
||||
|
||||
/** Gets the scope of this node. */
|
||||
final CfgScope getScope() { result = this.getBasicBlock().getScope() }
|
||||
|
||||
/** Gets the basic block that this control flow node belongs to. */
|
||||
BasicBlock getBasicBlock() { result.getANode() = this }
|
||||
|
||||
/** Gets a successor node of a given type, if any. */
|
||||
final CfgNode getASuccessor(SuccessorType t) { result = getASuccessor(this, t) }
|
||||
|
||||
/** Gets an immediate successor, if any. */
|
||||
final CfgNode getASuccessor() { result = this.getASuccessor(_) }
|
||||
|
||||
/** Gets an immediate predecessor node of a given flow type, if any. */
|
||||
final CfgNode getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
|
||||
|
||||
/** Gets an immediate predecessor, if any. */
|
||||
final CfgNode getAPredecessor() { result = this.getAPredecessor(_) }
|
||||
|
||||
/** Holds if this node has more than one predecessor. */
|
||||
final predicate isJoin() { strictcount(this.getAPredecessor()) > 1 }
|
||||
|
||||
/** Holds if this node has more than one successor. */
|
||||
final predicate isBranch() { strictcount(this.getASuccessor()) > 1 }
|
||||
}
|
||||
|
||||
/** Provides different types of control flow nodes. */
|
||||
module CfgNodes {
|
||||
/** An entry node for a given scope. */
|
||||
class EntryNode extends CfgNode, TEntryNode {
|
||||
private CfgScope scope;
|
||||
|
||||
EntryNode() { this = TEntryNode(scope) }
|
||||
|
||||
final override EntryBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() }
|
||||
|
||||
final override Location getLocation() { result = scope.getLocation() }
|
||||
|
||||
final override string toString() { result = "enter " + scope.getName() }
|
||||
}
|
||||
|
||||
/** An exit node for a given scope, annotated with the type of exit. */
|
||||
class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
|
||||
private CfgScope scope;
|
||||
private boolean normal;
|
||||
|
||||
AnnotatedExitNode() { this = TAnnotatedExitNode(scope, normal) }
|
||||
|
||||
/** Holds if this node represent a normal exit. */
|
||||
final predicate isNormal() { normal = true }
|
||||
|
||||
final override AnnotatedExitBasicBlock getBasicBlock() {
|
||||
result = CfgNode.super.getBasicBlock()
|
||||
}
|
||||
|
||||
final override Location getLocation() { result = scope.getLocation() }
|
||||
|
||||
final override string toString() {
|
||||
exists(string s |
|
||||
normal = true and s = "normal"
|
||||
or
|
||||
normal = false and s = "abnormal"
|
||||
|
|
||||
result = "exit " + scope.getName() + " (" + s + ")"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An exit node for a given scope. */
|
||||
class ExitNode extends CfgNode, TExitNode {
|
||||
private CfgScope scope;
|
||||
|
||||
ExitNode() { this = TExitNode(scope) }
|
||||
|
||||
final override Location getLocation() { result = scope.getLocation() }
|
||||
|
||||
final override string toString() { result = "exit " + scope.getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A node for an AST node.
|
||||
*
|
||||
* Each AST node maps to zero or more `AstCfgNode`s: zero when the node in unreachable
|
||||
* (dead) code or not important for control flow, and multiple when there are different
|
||||
* splits for the AST node.
|
||||
*/
|
||||
class AstCfgNode extends CfgNode, TAstNode {
|
||||
private Splits splits;
|
||||
private AstNode n;
|
||||
|
||||
AstCfgNode() { this = TAstNode(n, splits) }
|
||||
|
||||
final override AstNode getNode() { result = n }
|
||||
|
||||
final override string toString() {
|
||||
result = "[" + this.getSplitsString() + "] " + n.toString()
|
||||
or
|
||||
not exists(this.getSplitsString()) and result = n.toString()
|
||||
}
|
||||
|
||||
/** Gets a comma-separated list of strings for each split in this node, if any. */
|
||||
final string getSplitsString() {
|
||||
result = splits.toString() and
|
||||
result != ""
|
||||
}
|
||||
|
||||
/** Gets a split for this control flow node, if any. */
|
||||
final Split getASplit() { result = splits.getASplit() }
|
||||
}
|
||||
}
|
||||
|
||||
/** The type of a control flow successor. */
|
||||
class SuccessorType extends TSuccessorType {
|
||||
/** Gets a textual representation of successor type. */
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/** Provides different types of control flow successor types. */
|
||||
module SuccessorTypes {
|
||||
/** A normal control flow successor. */
|
||||
class NormalSuccessor extends SuccessorType, TSuccessorSuccessor {
|
||||
final override string toString() { result = "successor" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Boolean control flow successor.
|
||||
*
|
||||
* For example, in
|
||||
*
|
||||
* ```rb
|
||||
* if x >= 0
|
||||
* puts "positive"
|
||||
* else
|
||||
* puts "negative"
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* `x >= 0` has both a `true` successor and a `false` successor.
|
||||
*/
|
||||
class BooleanSuccessor extends SuccessorType, TBooleanSuccessor {
|
||||
/** Gets the Boolean value. */
|
||||
final boolean getValue() { this = TBooleanSuccessor(result) }
|
||||
|
||||
final override string toString() { result = getValue().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `return` control flow successor.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```rb
|
||||
* def sum(x,y)
|
||||
* return x + y
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* The exit node of `sum` is a `return` successor of the `return x + y`
|
||||
* statement.
|
||||
*/
|
||||
class ReturnSuccessor extends SuccessorType, TReturnSuccessor {
|
||||
final override string toString() { result = "return" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `break` control flow successor.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```rb
|
||||
* def m
|
||||
* while x >= 0
|
||||
* x -= 1
|
||||
* if num > 100
|
||||
* break
|
||||
* end
|
||||
* end
|
||||
* puts "done"
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* The node `puts "done"` is `break` successor of the node `break`.
|
||||
*/
|
||||
class BreakSuccessor extends SuccessorType, TBreakSuccessor {
|
||||
final override string toString() { result = "break" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `next` control flow successor.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```rb
|
||||
* def m
|
||||
* while x >= 0
|
||||
* x -= 1
|
||||
* if num > 100
|
||||
* next
|
||||
* end
|
||||
* end
|
||||
* puts "done"
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* The node `x >= 0` is `next` successor of the node `next`.
|
||||
*/
|
||||
class NextSuccessor extends SuccessorType, TNextSuccessor {
|
||||
final override string toString() { result = "next" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `redo` control flow successor.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```rb
|
||||
* def m
|
||||
* while x >= 0
|
||||
* x -= 1
|
||||
* if num > 100
|
||||
* redo
|
||||
* end
|
||||
* end
|
||||
* puts "done"
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* The node `x -= 1` is `redo` successor of the node `redo`.
|
||||
*/
|
||||
class RedoSuccessor extends SuccessorType, TRedoSuccessor {
|
||||
final override string toString() { result = "redo" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An exceptional control flow successor.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```rb
|
||||
* def m x
|
||||
* if x > 2
|
||||
* raise "x > 2"
|
||||
* end
|
||||
* puts "x <= 2"
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* The exit node of `m` is an exceptional successor of the node
|
||||
* `raise "x > 2"`.
|
||||
*/
|
||||
class RaiseSuccessor extends SuccessorType, TRaiseSuccessor {
|
||||
final override string toString() { result = "raise" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An exit control flow successor.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```rb
|
||||
* def m x
|
||||
* if x > 2
|
||||
* exit 1
|
||||
* end
|
||||
* puts "x <= 2"
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* The exit node of `m` is an exit successor of the node
|
||||
* `exit 1`.
|
||||
*/
|
||||
class ExitSuccessor extends SuccessorType, TExitSuccessor {
|
||||
final override string toString() { result = "exit" }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user