Files
codeql/ql/src/codeql_ruby/controlflow/CfgNodes.qll
2021-05-05 12:12:45 +01:00

330 lines
11 KiB
Plaintext

/** Provides classes representing nodes in a control flow graph. */
private import codeql_ruby.AST
private import codeql_ruby.controlflow.BasicBlocks
private import ControlFlowGraph
private import internal.ControlFlowGraphImpl
private import internal.Splitting
/** 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 }
}
/** 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 + " (" + 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 }
}
/**
* 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, TAstCfgNode {
private Splits splits;
private AstNode n;
AstCfgNode() { this = TAstCfgNode(n, splits) }
final override AstNode getNode() { result = n }
override Location getLocation() { result = n.getLocation() }
final override string toString() {
exists(string s | s = n.(AstNode).toString() |
result = "[" + this.getSplitsString() + "] " + s
or
not exists(this.getSplitsString()) and result = s
)
}
/** 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() }
}
/** A control-flow node that wraps an AST expression. */
class ExprCfgNode extends AstCfgNode {
Expr e;
ExprCfgNode() { e = this.getNode() }
/** Gets the underlying expression. */
Expr getExpr() { result = e }
}
/** A control-flow node that wraps a return-like statement. */
class ReturningCfgNode extends AstCfgNode {
ReturningStmt s;
ReturningCfgNode() { s = this.getNode() }
/** Gets the node of the returned value, if any. */
ExprCfgNode getReturnedValueNode() {
result = this.getAPredecessor() and
result.getNode() = s.getValue()
}
}
/**
* A class for mapping parent-child AST nodes to parent-child CFG nodes.
*/
abstract private class ExprChildMapping extends Expr {
/**
* Holds if `child` is a (possibly nested) child of this expression
* for which we would like to find a matching CFG child.
*/
abstract predicate relevantChild(Expr child);
private AstNode getAChildStar() {
result = this
or
result.getParent() = this.getAChildStar()
}
pragma[noinline]
private BasicBlock getABasicBlockInScope() {
result.getANode() = TAstCfgNode(this.getAChildStar(), _)
}
pragma[nomagic]
private predicate reachesBasicBlockBase(Expr child, CfgNode cfn, BasicBlock bb) {
this.relevantChild(child) and
cfn = this.getAControlFlowNode() and
bb.getANode() = cfn
}
pragma[nomagic]
private predicate reachesBasicBlock(Expr child, CfgNode cfn, BasicBlock bb) {
this.reachesBasicBlockBase(child, cfn, bb)
or
this.relevantChild(child) and
this.reachesBasicBlockRec(child, cfn, bb) and
bb = this.getABasicBlockInScope()
}
pragma[nomagic]
private predicate reachesBasicBlockRec(Expr child, CfgNode cfn, BasicBlock bb) {
exists(BasicBlock mid | this.reachesBasicBlock(child, cfn, mid) |
bb = mid.getASuccessor()
or
bb = mid.getAPredecessor()
)
}
/**
* Holds if there is a control-flow path from `cfn` to `cfnChild`, where `cfn`
* is a control-flow node for this expression, and `cfnChild` is a control-flow
* node for `child`.
*
* The path never escapes the syntactic scope of this expression.
*/
cached
predicate hasCfgChild(Expr child, CfgNode cfn, CfgNode cfnChild) {
exists(BasicBlock bb |
this.reachesBasicBlockBase(child, cfn, bb) and
cfnChild = bb.getANode() and
cfnChild = child.getAControlFlowNode()
)
or
exists(BasicBlock bb |
this.reachesBasicBlockRec(child, cfn, bb) and
cfnChild = bb.getANode() and
cfnChild = child.getAControlFlowNode()
)
}
}
/** Provides classes for control-flow nodes that wrap AST expressions. */
module ExprNodes {
// TODO: Add more classes
private class AssignmentExprChildMapping extends ExprChildMapping, Assignment {
override predicate relevantChild(Expr e) { e = this.getAnOperand() }
}
/** A control-flow node that wraps an `Assignment` AST expression. */
class AssignmentCfgNode extends ExprCfgNode {
override AssignmentExprChildMapping e;
final override Assignment getExpr() { result = ExprCfgNode.super.getExpr() }
/** Gets the LHS of this assignment. */
final ExprCfgNode getLhs() { e.hasCfgChild(e.getLeftOperand(), this, result) }
/** Gets the RHS of this assignment. */
final ExprCfgNode getRhs() { e.hasCfgChild(e.getRightOperand(), this, result) }
}
/** A control-flow node that wraps an `AssignExpr` AST expression. */
class AssignExprCfgNode extends AssignmentCfgNode {
AssignExprCfgNode() { this.getExpr() instanceof AssignExpr }
}
private class BinaryOperationExprChildMapping extends ExprChildMapping, BinaryOperation {
override predicate relevantChild(Expr e) { e = this.getAnOperand() }
}
/** A control-flow node that wraps a `BinaryOperation` AST expression. */
class BinaryOperationCfgNode extends ExprCfgNode {
override BinaryOperationExprChildMapping e;
final override BinaryOperation getExpr() { result = ExprCfgNode.super.getExpr() }
/** Gets the left operand of this binary operation. */
final ExprCfgNode getLeftOperand() { e.hasCfgChild(e.getLeftOperand(), this, result) }
/** Gets the right operand of this binary operation. */
final ExprCfgNode getRightOperand() { e.hasCfgChild(e.getRightOperand(), this, result) }
}
private class CallExprChildMapping extends ExprChildMapping, Call {
override predicate relevantChild(Expr e) {
e = [this.getAnArgument(), this.(MethodCall).getReceiver()]
}
}
/** A control-flow node that wraps a `Call` AST expression. */
class CallCfgNode extends ExprCfgNode {
override CallExprChildMapping e;
final override Call getExpr() { result = ExprCfgNode.super.getExpr() }
/** Gets the `n`th argument of this call. */
final ExprCfgNode getArgument(int n) { e.hasCfgChild(e.getArgument(n), this, result) }
/** Gets the receiver of this call. */
final ExprCfgNode getReceiver() { e.hasCfgChild(e.(MethodCall).getReceiver(), this, result) }
}
private class CaseExprChildMapping extends ExprChildMapping, CaseExpr {
override predicate relevantChild(Expr e) { e = this.getValue() or e = this.getBranch(_) }
}
/** A control-flow node that wraps a `MethodCall` AST expression. */
class MethodCallCfgNode extends CallCfgNode {
MethodCallCfgNode() { this.getExpr() instanceof MethodCall }
}
/** A control-flow node that wraps a `CaseExpr` AST expression. */
class CaseExprCfgNode extends ExprCfgNode {
override CaseExprChildMapping e;
final override CaseExpr getExpr() { result = ExprCfgNode.super.getExpr() }
/** Gets the expression being compared, if any. */
final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) }
/**
* Gets the `n`th branch of this case expression.
*/
final ExprCfgNode getBranch(int n) { e.hasCfgChild(e.getBranch(n), this, result) }
}
private class ConditionalExprChildMapping extends ExprChildMapping, ConditionalExpr {
override predicate relevantChild(Expr e) { e = this.getCondition() or e = this.getBranch(_) }
}
/** A control-flow node that wraps a `ConditionalExpr` AST expression. */
class ConditionalExprCfgNode extends ExprCfgNode {
override ConditionalExprChildMapping e;
final override ConditionalExpr getExpr() { result = ExprCfgNode.super.getExpr() }
/** Gets the condition expression. */
final ExprCfgNode getCondition() { e.hasCfgChild(e.getCondition(), this, result) }
/**
* Gets the branch of this conditional expression that is taken when the condition
* evaluates to cond, if any.
*/
final ExprCfgNode getBranch(boolean cond) { e.hasCfgChild(e.getBranch(cond), this, result) }
}
private class StmtSequenceChildMapping extends ExprChildMapping, StmtSequence {
override predicate relevantChild(Expr e) { e = this.getLastStmt() }
}
/** A control-flow node that wraps a `StmtSequence` AST expression. */
class StmtSequenceCfgNode extends ExprCfgNode {
override StmtSequenceChildMapping e;
final override StmtSequence getExpr() { result = ExprCfgNode.super.getExpr() }
/** Gets the last statement in this sequence, if any. */
final ExprCfgNode getLastStmt() { e.hasCfgChild(e.getLastStmt(), this, result) }
}
private class ForExprChildMapping extends ExprChildMapping, ForExpr {
override predicate relevantChild(Expr e) { e = this.getValue() }
}
/** A control-flow node that wraps a `ForExpr` AST expression. */
class ForExprCfgNode extends ExprCfgNode {
override ForExprChildMapping e;
final override ForExpr getExpr() { result = ExprCfgNode.super.getExpr() }
/** Gets the value being iterated over. */
final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) }
}
/** A control-flow node that wraps a `ParenthesizedExpr` AST expression. */
class ParenthesizedExprCfgNode extends StmtSequenceCfgNode {
ParenthesizedExprCfgNode() { this.getExpr() instanceof ParenthesizedExpr }
}
/** A control-flow node that wraps a `VariableReadAccess` AST expression. */
class VariableReadAccessCfgNode extends ExprCfgNode {
override VariableReadAccess e;
final override VariableReadAccess getExpr() { result = ExprCfgNode.super.getExpr() }
}
}