mirror of
https://github.com/github/codeql.git
synced 2025-12-18 01:33:15 +01:00
Make shared CFG construction library a parameterized module
This commit is contained in:
@@ -486,7 +486,6 @@
|
||||
],
|
||||
"CFG": [
|
||||
"csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll",
|
||||
"ruby/ql/lib/codeql/ruby/controlflow/internal/ControlFlowGraphImplShared.qll",
|
||||
"swift/ql/lib/codeql/swift/controlflow/internal/ControlFlowGraphImplShared.qll"
|
||||
],
|
||||
"TypeTracker": [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared::Consistency
|
||||
import codeql.ruby.controlflow.internal.ControlFlowGraphImpl::Consistency
|
||||
import codeql.ruby.AST
|
||||
import codeql.ruby.CFG
|
||||
import codeql.ruby.controlflow.internal.Completion
|
||||
import codeql.ruby.controlflow.internal.ControlFlowGraphImpl
|
||||
import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as CfgImpl
|
||||
|
||||
/**
|
||||
* All `Expr` nodes are `PostOrderTree`s
|
||||
@@ -14,7 +14,7 @@ query predicate nonPostOrderExpr(Expr e, string cls) {
|
||||
not e instanceof Namespace and
|
||||
not e instanceof Toplevel and
|
||||
exists(AstNode last, Completion c |
|
||||
last(e, last, c) and
|
||||
CfgImpl::last(e, last, c) and
|
||||
last != e and
|
||||
c instanceof NormalCompletion
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ private import codeql.ruby.CFG
|
||||
private import internal.AST
|
||||
private import internal.TreeSitter
|
||||
private import internal.Variable
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as CfgImpl
|
||||
|
||||
/**
|
||||
* A statement.
|
||||
@@ -15,10 +15,10 @@ class Stmt extends AstNode, TStmt {
|
||||
CfgNodes::AstCfgNode getAControlFlowNode() { result.getNode() = this }
|
||||
|
||||
/** Gets a control-flow entry node for this statement, if any */
|
||||
AstNode getAControlFlowEntryNode() { result = getAControlFlowEntryNode(this) }
|
||||
AstNode getAControlFlowEntryNode() { result = CfgImpl::getAControlFlowEntryNode(this) }
|
||||
|
||||
/** Gets the control-flow scope of this statement, if any. */
|
||||
CfgScope getCfgScope() { result = getCfgScope(this) }
|
||||
CfgScope getCfgScope() { result = CfgImpl::getCfgScope(this) }
|
||||
|
||||
/** Gets the enclosing callable, if any. */
|
||||
Callable getEnclosingCallable() { result = this.getCfgScope() }
|
||||
|
||||
@@ -4,7 +4,6 @@ private import codeql.ruby.AST
|
||||
private import codeql.ruby.ast.internal.AST
|
||||
private import codeql.ruby.ast.internal.TreeSitter
|
||||
private import codeql.ruby.controlflow.ControlFlowGraph
|
||||
private import internal.ControlFlowGraphImpl
|
||||
private import CfgNodes
|
||||
private import SuccessorTypes
|
||||
|
||||
|
||||
@@ -6,16 +6,16 @@ private import codeql.ruby.dataflow.SSA
|
||||
private import codeql.ruby.ast.internal.Constant
|
||||
private import codeql.ruby.ast.internal.Literal
|
||||
private import ControlFlowGraph
|
||||
private import internal.ControlFlowGraphImpl
|
||||
private import internal.ControlFlowGraphImpl as CfgImpl
|
||||
private import internal.Splitting
|
||||
|
||||
/** An entry node for a given scope. */
|
||||
class EntryNode extends CfgNode, TEntryNode {
|
||||
class EntryNode extends CfgNode, CfgImpl::TEntryNode {
|
||||
override string getAPrimaryQlClass() { result = "EntryNode" }
|
||||
|
||||
private CfgScope scope;
|
||||
|
||||
EntryNode() { this = TEntryNode(scope) }
|
||||
EntryNode() { this = CfgImpl::TEntryNode(scope) }
|
||||
|
||||
final override EntryBasicBlock getBasicBlock() { result = super.getBasicBlock() }
|
||||
|
||||
@@ -25,13 +25,13 @@ class EntryNode extends CfgNode, TEntryNode {
|
||||
}
|
||||
|
||||
/** An exit node for a given scope, annotated with the type of exit. */
|
||||
class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
|
||||
class AnnotatedExitNode extends CfgNode, CfgImpl::TAnnotatedExitNode {
|
||||
override string getAPrimaryQlClass() { result = "AnnotatedExitNode" }
|
||||
|
||||
private CfgScope scope;
|
||||
private boolean normal;
|
||||
|
||||
AnnotatedExitNode() { this = TAnnotatedExitNode(scope, normal) }
|
||||
AnnotatedExitNode() { this = CfgImpl::TAnnotatedExitNode(scope, normal) }
|
||||
|
||||
/** Holds if this node represent a normal exit. */
|
||||
final predicate isNormal() { normal = true }
|
||||
@@ -52,12 +52,12 @@ class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
|
||||
}
|
||||
|
||||
/** An exit node for a given scope. */
|
||||
class ExitNode extends CfgNode, TExitNode {
|
||||
class ExitNode extends CfgNode, CfgImpl::TExitNode {
|
||||
override string getAPrimaryQlClass() { result = "ExitNode" }
|
||||
|
||||
private CfgScope scope;
|
||||
|
||||
ExitNode() { this = TExitNode(scope) }
|
||||
ExitNode() { this = CfgImpl::TExitNode(scope) }
|
||||
|
||||
final override Location getLocation() { result = scope.getLocation() }
|
||||
|
||||
@@ -71,14 +71,14 @@ class ExitNode extends CfgNode, TExitNode {
|
||||
* (dead) code or not important for control flow, and multiple when there are different
|
||||
* splits for the AST node.
|
||||
*/
|
||||
class AstCfgNode extends CfgNode, TElementNode {
|
||||
class AstCfgNode extends CfgNode, CfgImpl::TElementNode {
|
||||
/** Gets the name of the primary QL class for this node. */
|
||||
override string getAPrimaryQlClass() { result = "AstCfgNode" }
|
||||
|
||||
private Splits splits;
|
||||
private CfgImpl::Splits splits;
|
||||
AstNode e;
|
||||
|
||||
AstCfgNode() { this = TElementNode(_, e, splits) }
|
||||
AstCfgNode() { this = CfgImpl::TElementNode(_, e, splits) }
|
||||
|
||||
final override AstNode getNode() { result = e }
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.controlflow.BasicBlocks
|
||||
private import SuccessorTypes
|
||||
private import internal.ControlFlowGraphImpl
|
||||
private import internal.ControlFlowGraphImpl as CfgImpl
|
||||
private import internal.Splitting
|
||||
private import internal.Completion
|
||||
|
||||
@@ -15,12 +15,12 @@ private import internal.Completion
|
||||
* Note that module declarations are not themselves CFG scopes, as they are part of
|
||||
* the CFG of the enclosing top-level or callable.
|
||||
*/
|
||||
class CfgScope extends Scope instanceof CfgScopeImpl {
|
||||
class CfgScope extends Scope instanceof CfgImpl::CfgScopeImpl {
|
||||
/** Gets the CFG scope that this scope is nested under, if any. */
|
||||
final CfgScope getOuterCfgScope() {
|
||||
exists(AstNode parent |
|
||||
parent = this.getParent() and
|
||||
result = getCfgScope(parent)
|
||||
result = CfgImpl::getCfgScope(parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ class CfgScope extends Scope instanceof CfgScopeImpl {
|
||||
*
|
||||
* Only nodes that can be reached from an entry point are included in the CFG.
|
||||
*/
|
||||
class CfgNode extends TCfgNode {
|
||||
class CfgNode extends CfgImpl::TCfgNode {
|
||||
/** Gets the name of the primary QL class for this node. */
|
||||
string getAPrimaryQlClass() { none() }
|
||||
|
||||
@@ -53,13 +53,13 @@ class CfgNode extends TCfgNode {
|
||||
final predicate isCondition() { exists(this.getASuccessor(any(ConditionalSuccessor bs))) }
|
||||
|
||||
/** Gets the scope of this node. */
|
||||
final CfgScope getScope() { result = getNodeCfgScope(this) }
|
||||
final CfgScope getScope() { result = CfgImpl::getNodeCfgScope(this) }
|
||||
|
||||
/** 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) }
|
||||
final CfgNode getASuccessor(SuccessorType t) { result = CfgImpl::getASuccessor(this, t) }
|
||||
|
||||
/** Gets an immediate successor, if any. */
|
||||
final CfgNode getASuccessor() { result = this.getASuccessor(_) }
|
||||
@@ -78,7 +78,7 @@ class CfgNode extends TCfgNode {
|
||||
}
|
||||
|
||||
/** The type of a control flow successor. */
|
||||
class SuccessorType extends TSuccessorType {
|
||||
class SuccessorType extends CfgImpl::TSuccessorType {
|
||||
/** Gets a textual representation of successor type. */
|
||||
string toString() { none() }
|
||||
}
|
||||
@@ -86,7 +86,7 @@ class SuccessorType extends TSuccessorType {
|
||||
/** Provides different types of control flow successor types. */
|
||||
module SuccessorTypes {
|
||||
/** A normal control flow successor. */
|
||||
class NormalSuccessor extends SuccessorType, TSuccessorSuccessor {
|
||||
class NormalSuccessor extends SuccessorType, CfgImpl::TSuccessorSuccessor {
|
||||
final override string toString() { result = "successor" }
|
||||
}
|
||||
|
||||
@@ -99,9 +99,9 @@ module SuccessorTypes {
|
||||
boolean value;
|
||||
|
||||
ConditionalSuccessor() {
|
||||
this = TBooleanSuccessor(value) or
|
||||
this = TEmptinessSuccessor(value) or
|
||||
this = TMatchingSuccessor(value)
|
||||
this = CfgImpl::TBooleanSuccessor(value) or
|
||||
this = CfgImpl::TEmptinessSuccessor(value) or
|
||||
this = CfgImpl::TMatchingSuccessor(value)
|
||||
}
|
||||
|
||||
/** Gets the Boolean value of this successor. */
|
||||
@@ -125,7 +125,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* `x >= 0` has both a `true` successor and a `false` successor.
|
||||
*/
|
||||
class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor { }
|
||||
class BooleanSuccessor extends ConditionalSuccessor, CfgImpl::TBooleanSuccessor { }
|
||||
|
||||
/**
|
||||
* An emptiness control flow successor.
|
||||
@@ -158,7 +158,7 @@ module SuccessorTypes {
|
||||
* \___/
|
||||
* ```
|
||||
*/
|
||||
class EmptinessSuccessor extends ConditionalSuccessor, TEmptinessSuccessor {
|
||||
class EmptinessSuccessor extends ConditionalSuccessor, CfgImpl::TEmptinessSuccessor {
|
||||
override string toString() { if value = true then result = "empty" else result = "non-empty" }
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ module SuccessorTypes {
|
||||
* puts "one" puts "not one"
|
||||
* ```
|
||||
*/
|
||||
class MatchingSuccessor extends ConditionalSuccessor, TMatchingSuccessor {
|
||||
class MatchingSuccessor extends ConditionalSuccessor, CfgImpl::TMatchingSuccessor {
|
||||
override string toString() { if value = true then result = "match" else result = "no-match" }
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ module SuccessorTypes {
|
||||
* The exit node of `sum` is a `return` successor of the `return x + y`
|
||||
* statement.
|
||||
*/
|
||||
class ReturnSuccessor extends SuccessorType, TReturnSuccessor {
|
||||
class ReturnSuccessor extends SuccessorType, CfgImpl::TReturnSuccessor {
|
||||
final override string toString() { result = "return" }
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* The node `puts "done"` is `break` successor of the node `break`.
|
||||
*/
|
||||
class BreakSuccessor extends SuccessorType, TBreakSuccessor {
|
||||
class BreakSuccessor extends SuccessorType, CfgImpl::TBreakSuccessor {
|
||||
final override string toString() { result = "break" }
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* The node `x >= 0` is `next` successor of the node `next`.
|
||||
*/
|
||||
class NextSuccessor extends SuccessorType, TNextSuccessor {
|
||||
class NextSuccessor extends SuccessorType, CfgImpl::TNextSuccessor {
|
||||
final override string toString() { result = "next" }
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* The node `x -= 1` is `redo` successor of the node `redo`.
|
||||
*/
|
||||
class RedoSuccessor extends SuccessorType, TRedoSuccessor {
|
||||
class RedoSuccessor extends SuccessorType, CfgImpl::TRedoSuccessor {
|
||||
final override string toString() { result = "redo" }
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ module SuccessorTypes {
|
||||
*
|
||||
* The node `puts "Retry"` is `retry` successor of the node `retry`.
|
||||
*/
|
||||
class RetrySuccessor extends SuccessorType, TRetrySuccessor {
|
||||
class RetrySuccessor extends SuccessorType, CfgImpl::TRetrySuccessor {
|
||||
final override string toString() { result = "retry" }
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ module SuccessorTypes {
|
||||
* The exit node of `m` is an exceptional successor of the node
|
||||
* `raise "x > 2"`.
|
||||
*/
|
||||
class RaiseSuccessor extends SuccessorType, TRaiseSuccessor {
|
||||
class RaiseSuccessor extends SuccessorType, CfgImpl::TRaiseSuccessor {
|
||||
final override string toString() { result = "raise" }
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ module SuccessorTypes {
|
||||
* The exit node of `m` is an exit successor of the node
|
||||
* `exit 1`.
|
||||
*/
|
||||
class ExitSuccessor extends SuccessorType, TExitSuccessor {
|
||||
class ExitSuccessor extends SuccessorType, CfgImpl::TExitSuccessor {
|
||||
final override string toString() { result = "exit" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ private import codeql.ruby.AST
|
||||
private import codeql.ruby.ast.internal.AST
|
||||
private import codeql.ruby.ast.internal.Control
|
||||
private import codeql.ruby.controlflow.ControlFlowGraph
|
||||
private import ControlFlowGraphImpl
|
||||
private import ControlFlowGraphImpl as CfgImpl
|
||||
private import NonReturning
|
||||
private import SuccessorTypes
|
||||
|
||||
@@ -53,7 +53,7 @@ private predicate nestedEnsureCompletion(TCompletion outer, int nestLevel) {
|
||||
or
|
||||
outer = TExitCompletion()
|
||||
) and
|
||||
nestLevel = any(Trees::BodyStmtTree t).getNestLevel()
|
||||
nestLevel = any(CfgImpl::Trees::BodyStmtTree t).getNestLevel()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
@@ -72,7 +72,7 @@ private predicate completionIsValidForStmt(AstNode n, Completion c) {
|
||||
}
|
||||
|
||||
private AstNode getARescuableBodyChild() {
|
||||
exists(Trees::BodyStmtTree bst | result = bst.getBodyChild(_, true) |
|
||||
exists(CfgImpl::Trees::BodyStmtTree bst | result = bst.getBodyChild(_, true) |
|
||||
exists(bst.getARescue())
|
||||
or
|
||||
exists(bst.getEnsure())
|
||||
@@ -247,7 +247,7 @@ private predicate inMatchingContext(AstNode n) {
|
||||
or
|
||||
n = any(ReferencePattern p).getExpr()
|
||||
or
|
||||
n.(Trees::DefaultValueParameterTree).hasDefaultValue()
|
||||
n.(CfgImpl::Trees::DefaultValueParameterTree).hasDefaultValue()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,74 +0,0 @@
|
||||
private import codeql.ruby.AST as RB
|
||||
private import ControlFlowGraphImpl as Impl
|
||||
private import Completion as Comp
|
||||
private import codeql.ruby.ast.internal.Synthesis
|
||||
private import Splitting as Splitting
|
||||
private import codeql.ruby.CFG as Cfg
|
||||
|
||||
/** The base class for `ControlFlowTree`. */
|
||||
class ControlFlowTreeBase extends RB::AstNode {
|
||||
ControlFlowTreeBase() { not any(Synthesis s).excludeFromControlFlowTree(this) }
|
||||
}
|
||||
|
||||
class ControlFlowElement = RB::AstNode;
|
||||
|
||||
class Completion = Comp::Completion;
|
||||
|
||||
/**
|
||||
* Hold if `c` represents normal evaluation of a statement or an
|
||||
* expression.
|
||||
*/
|
||||
predicate completionIsNormal(Completion c) { c instanceof Comp::NormalCompletion }
|
||||
|
||||
/**
|
||||
* Hold if `c` represents simple (normal) evaluation of a statement or an
|
||||
* expression.
|
||||
*/
|
||||
predicate completionIsSimple(Completion c) { c instanceof Comp::SimpleCompletion }
|
||||
|
||||
/** Holds if `c` is a valid completion for `e`. */
|
||||
predicate completionIsValidFor(Completion c, ControlFlowElement e) { c.isValidFor(e) }
|
||||
|
||||
class CfgScope = Cfg::CfgScope;
|
||||
|
||||
predicate getCfgScope = Impl::getCfgScope/1;
|
||||
|
||||
/** Holds if `first` is first executed when entering `scope`. */
|
||||
predicate scopeFirst(CfgScope scope, ControlFlowElement first) {
|
||||
scope.(Impl::CfgScopeImpl).entry(first)
|
||||
}
|
||||
|
||||
/** Holds if `scope` is exited when `last` finishes with completion `c`. */
|
||||
predicate scopeLast(CfgScope scope, ControlFlowElement last, Completion c) {
|
||||
scope.(Impl::CfgScopeImpl).exit(last, c)
|
||||
}
|
||||
|
||||
/** The maximum number of splits allowed for a given node. */
|
||||
int maxSplits() { result = 5 }
|
||||
|
||||
class SplitKindBase = Splitting::TSplitKind;
|
||||
|
||||
class Split = Splitting::Split;
|
||||
|
||||
class SuccessorType = Cfg::SuccessorType;
|
||||
|
||||
/** Gets a successor type that matches completion `c`. */
|
||||
SuccessorType getAMatchingSuccessorType(Completion c) { result = c.getAMatchingSuccessorType() }
|
||||
|
||||
/**
|
||||
* Hold if `c` represents simple (normal) evaluation of a statement or an
|
||||
* expression.
|
||||
*/
|
||||
predicate successorTypeIsSimple(SuccessorType t) {
|
||||
t instanceof Cfg::SuccessorTypes::NormalSuccessor
|
||||
}
|
||||
|
||||
/** Holds if `t` is an abnormal exit type out of a CFG scope. */
|
||||
predicate isAbnormalExitType(SuccessorType t) {
|
||||
t instanceof Cfg::SuccessorTypes::RaiseSuccessor or
|
||||
t instanceof Cfg::SuccessorTypes::ExitSuccessor
|
||||
}
|
||||
|
||||
class Location = RB::Location;
|
||||
|
||||
class Node = Cfg::CfgNode;
|
||||
@@ -2,7 +2,7 @@
|
||||
* Provides classes and predicates relevant for splitting the control flow graph.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.AST as Ast
|
||||
private import Completion
|
||||
private import ControlFlowGraphImpl
|
||||
private import SuccessorTypes
|
||||
@@ -64,31 +64,36 @@ private module ConditionalCompletionSplitting {
|
||||
|
||||
int getNextListOrder() { result = 1 }
|
||||
|
||||
private class ConditionalCompletionSplitImpl extends SplitImpl, ConditionalCompletionSplit {
|
||||
private class ConditionalCompletionSplitImpl extends SplitImpl instanceof ConditionalCompletionSplit
|
||||
{
|
||||
ConditionalCompletion completion;
|
||||
|
||||
ConditionalCompletionSplitImpl() { this = TConditionalCompletionSplit(completion) }
|
||||
|
||||
override ConditionalCompletionSplitKind getKind() { any() }
|
||||
|
||||
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
|
||||
succ(pred, succ, c) and
|
||||
last(succ, _, completion) and
|
||||
(
|
||||
last(succ.(NotExpr).getOperand(), pred, c) and
|
||||
last(succ.(Ast::NotExpr).getOperand(), pred, c) and
|
||||
completion.(BooleanCompletion).getDual() = c
|
||||
or
|
||||
last(succ.(LogicalAndExpr).getAnOperand(), pred, c) and
|
||||
last(succ.(Ast::LogicalAndExpr).getAnOperand(), pred, c) and
|
||||
completion = c
|
||||
or
|
||||
last(succ.(LogicalOrExpr).getAnOperand(), pred, c) and
|
||||
last(succ.(Ast::LogicalOrExpr).getAnOperand(), pred, c) and
|
||||
completion = c
|
||||
or
|
||||
last(succ.(StmtSequence).getLastStmt(), pred, c) and
|
||||
last(succ.(Ast::StmtSequence).getLastStmt(), pred, c) and
|
||||
completion = c
|
||||
or
|
||||
last(succ.(ConditionalExpr).getBranch(_), pred, c) and
|
||||
last(succ.(Ast::ConditionalExpr).getBranch(_), pred, c) and
|
||||
completion = c
|
||||
)
|
||||
or
|
||||
succ(pred, succ, c) and
|
||||
succ instanceof WhenClause and
|
||||
succ instanceof Ast::WhenClause and
|
||||
completion = c
|
||||
}
|
||||
|
||||
@@ -144,7 +149,7 @@ module EnsureSplitting {
|
||||
/** Holds if this node is the entry node in the `ensure` block it belongs to. */
|
||||
predicate isEntryNode() { first(block.getEnsure(), this) }
|
||||
|
||||
BodyStmt getBlock() { result = block }
|
||||
Ast::BodyStmt getBlock() { result = block }
|
||||
|
||||
pragma[noinline]
|
||||
predicate isEntered(AstNode pred, int nestLevel, Completion c) {
|
||||
@@ -221,12 +226,12 @@ module EnsureSplitting {
|
||||
override string toString() { result = "ensure (" + nestLevel + ")" }
|
||||
}
|
||||
|
||||
private class EnsureSplitImpl extends SplitImpl, EnsureSplit {
|
||||
override EnsureSplitKind getKind() { result.getNestLevel() = this.getNestLevel() }
|
||||
private class EnsureSplitImpl extends SplitImpl instanceof EnsureSplit {
|
||||
override EnsureSplitKind getKind() { result.getNestLevel() = super.getNestLevel() }
|
||||
|
||||
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
|
||||
succ.(EnsureNode).isEntered(pred, this.getNestLevel(), c) and
|
||||
this.getType().isSplitForEntryCompletion(c)
|
||||
succ.(EnsureNode).isEntered(pred, super.getNestLevel(), c) and
|
||||
super.getType().isSplitForEntryCompletion(c)
|
||||
}
|
||||
|
||||
override predicate hasEntryScope(CfgScope scope, AstNode first) { none() }
|
||||
@@ -253,8 +258,8 @@ module EnsureSplitting {
|
||||
*/
|
||||
private predicate exit(AstNode pred, Completion c, boolean inherited) {
|
||||
exists(Trees::BodyStmtTree block, EnsureSplitType type |
|
||||
this.exit0(pred, block, this.getNestLevel(), c) and
|
||||
type = this.getType()
|
||||
this.exit0(pred, block, super.getNestLevel(), c) and
|
||||
type = super.getType()
|
||||
|
|
||||
if last(block.getEnsure(), pred, c)
|
||||
then
|
||||
@@ -302,9 +307,9 @@ module EnsureSplitting {
|
||||
// split must be able to exit with a `return` completion.
|
||||
this.appliesToPredecessor(pred) and
|
||||
exists(EnsureSplitImpl outer |
|
||||
outer.getNestLevel() = this.getNestLevel() - 1 and
|
||||
outer.(EnsureSplit).getNestLevel() = super.getNestLevel() - 1 and
|
||||
outer.exit(pred, c, inherited) and
|
||||
this.getType() instanceof NormalSuccessor and
|
||||
super.getType() instanceof NormalSuccessor and
|
||||
inherited = true
|
||||
)
|
||||
}
|
||||
@@ -335,10 +340,10 @@ module EnsureSplitting {
|
||||
if en.isEntryNode() and en.getBlock() != pred.(EnsureNode).getBlock()
|
||||
then
|
||||
// entering a nested `ensure` block
|
||||
en.getNestLevel() > this.getNestLevel()
|
||||
en.getNestLevel() > super.getNestLevel()
|
||||
else
|
||||
// staying in the same (possibly nested) `ensure` block as `pred`
|
||||
en.getNestLevel() >= this.getNestLevel()
|
||||
en.getNestLevel() >= super.getNestLevel()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
private import codeql.ssa.Ssa as SsaImplCommon
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG as Cfg
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared as ControlFlowGraphImplShared
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as ControlFlowGraphImpl
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
private import codeql.ruby.ast.Variable
|
||||
private import Cfg::CfgNodes::ExprNodes
|
||||
@@ -124,7 +124,7 @@ private predicate capturedExitRead(Cfg::AnnotatedExitBasicBlock bb, int i, Local
|
||||
private predicate namespaceSelfExitRead(Cfg::AnnotatedExitBasicBlock bb, int i, SelfVariable v) {
|
||||
exists(Namespace ns, AstNode last |
|
||||
v.getDeclaringScope() = ns and
|
||||
last = ControlFlowGraphImplShared::getAControlFlowExitNode(ns) and
|
||||
last = ControlFlowGraphImpl::getAControlFlowExitNode(ns) and
|
||||
if last = ns
|
||||
then bb.getNode(i).getAPredecessor().getNode() = last
|
||||
else bb.getNode(i).getNode() = last
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @tags ide-contextual-queries/print-cfg
|
||||
*/
|
||||
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared::TestOutput
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl::TestOutput
|
||||
private import codeql.IDEContextual
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ dbscheme: ruby.dbscheme
|
||||
upgrades: upgrades
|
||||
library: true
|
||||
dependencies:
|
||||
codeql/controlflow: ${workspace}
|
||||
codeql/dataflow: ${workspace}
|
||||
codeql/mad: ${workspace}
|
||||
codeql/regex: ${workspace}
|
||||
|
||||
@@ -1,9 +1,133 @@
|
||||
/** Provides language-independent definitions for AST-to-CFG construction. */
|
||||
/**
|
||||
* Provides a shared interface and implementation for constructing control-flow graphs
|
||||
* (CFGs) from abstract syntax trees (ASTs).
|
||||
*/
|
||||
|
||||
private import ControlFlowGraphImplSpecific
|
||||
private import codeql.util.Location
|
||||
|
||||
/** An element with associated control flow. */
|
||||
abstract class ControlFlowTree extends ControlFlowTreeBase {
|
||||
/** Provides the language-specific input specification. */
|
||||
signature module InputSig<LocationSig Location> {
|
||||
/** The base class for `ControlFlowTree`. */
|
||||
class ControlFlowTreeBase {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString();
|
||||
|
||||
/** Gets the location of this element. */
|
||||
Location getLocation();
|
||||
}
|
||||
|
||||
/** An AST node. */
|
||||
class ControlFlowElement extends ControlFlowTreeBase;
|
||||
|
||||
/** A control-flow completion. */
|
||||
class Completion {
|
||||
/** Gets a textual representation of this completion. */
|
||||
string toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hold if `c` represents normal evaluation of a statement or an
|
||||
* expression.
|
||||
*/
|
||||
predicate completionIsNormal(Completion c);
|
||||
|
||||
/**
|
||||
* Hold if `c` represents simple (normal) evaluation of a statement or an
|
||||
* expression.
|
||||
*/
|
||||
predicate completionIsSimple(Completion c);
|
||||
|
||||
/** Holds if `c` is a valid completion for `e`. */
|
||||
predicate completionIsValidFor(Completion c, ControlFlowElement e);
|
||||
|
||||
/** A CFG scope. Each CFG scope gets its own control flow graph. */
|
||||
class CfgScope {
|
||||
/** Gets a textual representation of this scope. */
|
||||
string toString();
|
||||
|
||||
/** Gets the location of this scope. */
|
||||
Location getLocation();
|
||||
}
|
||||
|
||||
/** Gets the CFG scope of AST node `n`. */
|
||||
CfgScope getCfgScope(ControlFlowElement n);
|
||||
|
||||
/** Holds if `first` is first executed when entering `scope`. */
|
||||
predicate scopeFirst(CfgScope scope, ControlFlowElement first);
|
||||
|
||||
/** Holds if `scope` is exited when `last` finishes with completion `c`. */
|
||||
predicate scopeLast(CfgScope scope, ControlFlowElement last, Completion c);
|
||||
|
||||
/** Gets the maximum number of splits allowed for a given node. */
|
||||
default int maxSplits() { result = 5 }
|
||||
|
||||
/** The base class of `SplitKind`. */
|
||||
class SplitKindBase;
|
||||
|
||||
/** A split. */
|
||||
class Split {
|
||||
/** Gets a textual representation of this split. */
|
||||
string toString();
|
||||
}
|
||||
|
||||
/** A type of a control flow successor. */
|
||||
class SuccessorType {
|
||||
/** Gets a textual representation of this successor type. */
|
||||
string toString();
|
||||
}
|
||||
|
||||
/** Gets a successor type that matches completion `c`. */
|
||||
SuccessorType getAMatchingSuccessorType(Completion c);
|
||||
|
||||
/**
|
||||
* Hold if `t` represents simple (normal) evaluation of a statement or an
|
||||
* expression.
|
||||
*/
|
||||
predicate successorTypeIsSimple(SuccessorType t);
|
||||
|
||||
/** Holds if `t` is an abnormal exit type out of a CFG scope. */
|
||||
predicate isAbnormalExitType(SuccessorType t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a shared interface for constructing control-flow graphs (CFGs) from
|
||||
* abstract syntax trees (ASTs).
|
||||
*
|
||||
* The implementation is centered around the concept of a _completion_, which
|
||||
* models how the execution of a statement or expression terminates.
|
||||
*
|
||||
* The CFG is built by structural recursion over the AST, using the abstract class
|
||||
* `ControlFlowTree`. 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 = n.first()` and `k = n.last(_)`,
|
||||
* 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 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 completion representing a
|
||||
* loop break will be caught up by its surrounding loop and turned into a normal
|
||||
* completion.
|
||||
*/
|
||||
module Make<LocationSig Location, InputSig<Location> Input> {
|
||||
private import Input
|
||||
|
||||
final private class ControlFlowTreeBaseFinal = ControlFlowTreeBase;
|
||||
|
||||
/** An element with associated control flow. */
|
||||
abstract class ControlFlowTree extends ControlFlowTreeBaseFinal {
|
||||
/** Holds if `first` is the first element executed within this element. */
|
||||
pragma[nomagic]
|
||||
abstract predicate first(ControlFlowElement first);
|
||||
@@ -24,19 +148,19 @@ abstract class ControlFlowTree extends ControlFlowTreeBase {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
abstract predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Holds if `first` is the first element executed within control flow
|
||||
* element `cft`.
|
||||
*/
|
||||
predicate first(ControlFlowTree cft, ControlFlowElement first) { cft.first(first) }
|
||||
predicate first(ControlFlowTree cft, ControlFlowElement first) { cft.first(first) }
|
||||
|
||||
/**
|
||||
/**
|
||||
* Holds if `last` with completion `c` is a potential last element executed
|
||||
* within control flow element `cft`.
|
||||
*/
|
||||
predicate last(ControlFlowTree cft, ControlFlowElement last, Completion c) {
|
||||
predicate last(ControlFlowTree cft, ControlFlowElement last, Completion c) {
|
||||
cft.last(last, c)
|
||||
or
|
||||
exists(ControlFlowElement cfe |
|
||||
@@ -44,36 +168,36 @@ predicate last(ControlFlowTree cft, ControlFlowElement last, Completion c) {
|
||||
last(cfe, last, c) and
|
||||
not completionIsNormal(c)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Holds if `succ` is a control flow successor for `pred`, given that `pred`
|
||||
* finishes with completion `c`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
|
||||
pragma[nomagic]
|
||||
predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
|
||||
any(ControlFlowTree cft).succ(pred, succ, c)
|
||||
}
|
||||
}
|
||||
|
||||
/** An element that is executed in pre-order. */
|
||||
abstract class PreOrderTree extends ControlFlowTree {
|
||||
/** An element that is executed in pre-order, typically used for statements. */
|
||||
abstract class PreOrderTree extends ControlFlowTree {
|
||||
final override predicate first(ControlFlowElement first) { first = this }
|
||||
}
|
||||
}
|
||||
|
||||
/** An element that is executed in post-order. */
|
||||
abstract class PostOrderTree extends ControlFlowTree {
|
||||
/** An element that is executed in post-order, typically used for expressions. */
|
||||
abstract class PostOrderTree extends ControlFlowTree {
|
||||
override predicate last(ControlFlowElement last, Completion c) {
|
||||
last = this and
|
||||
completionIsValidFor(c, last)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* An element where the children are evaluated following a standard left-to-right
|
||||
* evaluation. The actual evaluation order is determined by the predicate
|
||||
* `getChildElement()`.
|
||||
*/
|
||||
abstract class StandardTree extends ControlFlowTree {
|
||||
abstract class StandardTree extends ControlFlowTree {
|
||||
/** Gets the `i`th child element, in order of evaluation. */
|
||||
abstract ControlFlowElement getChildElement(int i);
|
||||
|
||||
@@ -112,10 +236,10 @@ abstract class StandardTree extends ControlFlowTree {
|
||||
first(this.getChildElementRanked(i + 1), succ)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A standard element that is executed in pre-order. */
|
||||
abstract class StandardPreOrderTree extends StandardTree, PreOrderTree {
|
||||
/** A standard element that is executed in pre-order. */
|
||||
abstract class StandardPreOrderTree extends StandardTree, PreOrderTree {
|
||||
override predicate last(ControlFlowElement last, Completion c) {
|
||||
last(this.getLastChildElement(), last, c)
|
||||
or
|
||||
@@ -131,10 +255,10 @@ abstract class StandardPreOrderTree extends StandardTree, PreOrderTree {
|
||||
first(this.getFirstChildElement(), succ) and
|
||||
completionIsSimple(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A standard element that is executed in post-order. */
|
||||
abstract class StandardPostOrderTree extends StandardTree, PostOrderTree {
|
||||
/** A standard element that is executed in post-order. */
|
||||
abstract class StandardPostOrderTree extends StandardTree, PostOrderTree {
|
||||
override predicate first(ControlFlowElement first) {
|
||||
first(this.getFirstChildElement(), first)
|
||||
or
|
||||
@@ -149,32 +273,34 @@ abstract class StandardPostOrderTree extends StandardTree, PostOrderTree {
|
||||
succ = this and
|
||||
completionIsNormal(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** An element that is a leaf in the control flow graph. */
|
||||
abstract class LeafTree extends PreOrderTree, PostOrderTree {
|
||||
/** An element that is a leaf in the control flow graph. */
|
||||
abstract class LeafTree extends PreOrderTree, PostOrderTree {
|
||||
override predicate propagatesAbnormal(ControlFlowElement child) { none() }
|
||||
|
||||
override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) { none() }
|
||||
}
|
||||
override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Holds if split kinds `sk1` and `sk2` may overlap. That is, they may apply
|
||||
* to at least one common AST node inside `scope`.
|
||||
*/
|
||||
private predicate overlapping(CfgScope scope, SplitKind sk1, SplitKind sk2) {
|
||||
private predicate overlapping(CfgScope scope, SplitKind sk1, SplitKind sk2) {
|
||||
exists(ControlFlowElement e |
|
||||
sk1.appliesTo(e) and
|
||||
sk2.appliesTo(e) and
|
||||
scope = getCfgScope(e)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* A split kind. Each control flow node can have at most one split of a
|
||||
* given kind.
|
||||
*/
|
||||
abstract class SplitKind extends SplitKindBase {
|
||||
abstract class SplitKind instanceof SplitKindBase {
|
||||
/** Gets a split of this kind. */
|
||||
SplitImpl getASplit() { result.getKind() = this }
|
||||
|
||||
@@ -189,7 +315,8 @@ abstract class SplitKind extends SplitKindBase {
|
||||
|
||||
/** Gets the rank of this split kind among all overlapping kinds for `c`. */
|
||||
private int getRank(CfgScope scope) {
|
||||
this = rank[result](SplitKind sk | overlapping(scope, this, sk) | sk order by sk.getListOrder())
|
||||
this =
|
||||
rank[result](SplitKind sk | overlapping(scope, this, sk) | sk order by sk.getListOrder())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,10 +339,12 @@ abstract class SplitKind extends SplitKindBase {
|
||||
|
||||
/** Gets a textual representation of this split kind. */
|
||||
abstract string toString();
|
||||
}
|
||||
}
|
||||
|
||||
/** An interface for implementing an entity to split on. */
|
||||
abstract class SplitImpl extends Split {
|
||||
final private class SplitFinal = Split;
|
||||
|
||||
/** An interface for implementing an entity to split on. */
|
||||
abstract class SplitImpl extends SplitFinal {
|
||||
/** Gets the kind of this split. */
|
||||
abstract SplitKind getKind();
|
||||
|
||||
@@ -268,19 +397,22 @@ abstract class SplitImpl extends Split {
|
||||
exists(ControlFlowElement pred | this.appliesTo(pred) | this.hasSuccessor(pred, cfe, _))
|
||||
}
|
||||
|
||||
/** The `succ` relation restricted to predecessors `pred` that this split applies to. */
|
||||
/**
|
||||
* Holds if `succ` is a control flow successor for `pred`, given that `pred`
|
||||
* finishes with completion `c`, and this split applies to `pred`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
final predicate appliesSucc(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
|
||||
this.appliesTo(pred) and
|
||||
succ(pred, succ, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* A set of control flow node splits. The set is represented by a list of splits,
|
||||
* ordered by ascending rank.
|
||||
*/
|
||||
class Splits extends TSplits {
|
||||
class Splits extends TSplits {
|
||||
/** Gets a textual representation of this set of splits. */
|
||||
string toString() { result = splitsToString(this) }
|
||||
|
||||
@@ -292,11 +424,11 @@ class Splits extends TSplits {
|
||||
result = tail.getASplit()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private predicate succEntrySplitsFromRank(
|
||||
private predicate succEntrySplitsFromRank(
|
||||
CfgScope pred, ControlFlowElement succ, Splits splits, int rnk
|
||||
) {
|
||||
) {
|
||||
splits = TSplitsNil() and
|
||||
scopeFirst(pred, succ) and
|
||||
rnk = 0
|
||||
@@ -304,24 +436,24 @@ private predicate succEntrySplitsFromRank(
|
||||
exists(SplitImpl head, Splits tail | succEntrySplitsCons(pred, succ, head, tail, rnk) |
|
||||
splits = TSplitsCons(head, tail)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private predicate succEntrySplitsCons(
|
||||
private predicate succEntrySplitsCons(
|
||||
CfgScope pred, ControlFlowElement succ, SplitImpl head, Splits tail, int rnk
|
||||
) {
|
||||
) {
|
||||
succEntrySplitsFromRank(pred, succ, tail, rnk - 1) and
|
||||
head.hasEntryScope(pred, succ) and
|
||||
rnk = head.getKind().getListRank(succ)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Holds if `succ` with splits `succSplits` is the first element that is executed
|
||||
* when entering callable `pred`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate succEntrySplits(
|
||||
pragma[noinline]
|
||||
private predicate succEntrySplits(
|
||||
CfgScope pred, ControlFlowElement succ, Splits succSplits, SuccessorType t
|
||||
) {
|
||||
) {
|
||||
exists(int rnk |
|
||||
scopeFirst(pred, succ) and
|
||||
successorTypeIsSimple(t) and
|
||||
@@ -330,17 +462,18 @@ private predicate succEntrySplits(
|
||||
rnk = 0 and
|
||||
not any(SplitImpl split).hasEntryScope(pred, succ)
|
||||
or
|
||||
rnk = max(SplitImpl split | split.hasEntryScope(pred, succ) | split.getKind().getListRank(succ))
|
||||
rnk =
|
||||
max(SplitImpl split | split.hasEntryScope(pred, succ) | split.getKind().getListRank(succ))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Holds if `pred` with splits `predSplits` can exit the enclosing callable
|
||||
* `succ` with type `t`.
|
||||
*/
|
||||
private predicate succExitSplits(
|
||||
private predicate succExitSplits(
|
||||
ControlFlowElement pred, Splits predSplits, CfgScope succ, SuccessorType t
|
||||
) {
|
||||
) {
|
||||
exists(Reachability::SameSplitsBlock b, Completion c | pred = b.getAnElement() |
|
||||
b.isReachable(succ, predSplits) and
|
||||
t = getAMatchingSuccessorType(c) and
|
||||
@@ -349,9 +482,9 @@ private predicate succExitSplits(
|
||||
predSplit.hasExitScope(succ, pred, c)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Provides a predicate for the successor relation with split information,
|
||||
* as well as logic used to construct the type `TSplits` representing sets
|
||||
* of splits. Only sets of splits that can be reached are constructed, hence
|
||||
@@ -393,7 +526,7 @@ private predicate succExitSplits(
|
||||
*
|
||||
* The predicates in this module are named after the cases above.
|
||||
*/
|
||||
private module SuccSplits {
|
||||
private module SuccSplits {
|
||||
private predicate succInvariant1(
|
||||
Reachability::SameSplitsBlock b, ControlFlowElement pred, Splits predSplits,
|
||||
ControlFlowElement succ, Completion c
|
||||
@@ -427,7 +560,8 @@ private module SuccSplits {
|
||||
* to avoid negative recursion.
|
||||
*/
|
||||
private predicate case1bForall(
|
||||
ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, Splits except
|
||||
ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c,
|
||||
Splits except
|
||||
) {
|
||||
case1b0(pred, predSplits, succ, c) and
|
||||
except = predSplits
|
||||
@@ -489,8 +623,8 @@ private module SuccSplits {
|
||||
* to avoid negative recursion.
|
||||
*/
|
||||
private predicate case2aNoneInheritedOfKindForall(
|
||||
ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, SplitKind sk,
|
||||
Splits except
|
||||
ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c,
|
||||
SplitKind sk, Splits except
|
||||
) {
|
||||
case2aux(pred, predSplits, succ, c) and
|
||||
sk.appliesTo(succ) and
|
||||
@@ -517,7 +651,8 @@ private module SuccSplits {
|
||||
/** Holds if `succSplits` should not have a split of kind `sk`. */
|
||||
pragma[nomagic]
|
||||
private predicate case2aNoneOfKind(
|
||||
ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, SplitKind sk
|
||||
ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c,
|
||||
SplitKind sk
|
||||
) {
|
||||
// None inherited from predecessor
|
||||
case2aNoneInheritedOfKindForall(pred, predSplits, succ, c, sk, TSplitsNil()) and
|
||||
@@ -546,7 +681,8 @@ private module SuccSplits {
|
||||
/** Gets a split that should be in `succSplits`. */
|
||||
pragma[nomagic]
|
||||
private SplitImpl case2aSome(
|
||||
ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, SplitKind sk
|
||||
ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c,
|
||||
SplitKind sk
|
||||
) {
|
||||
(
|
||||
// Inherited from predecessor
|
||||
@@ -624,7 +760,8 @@ private module SuccSplits {
|
||||
* to avoid negative recursion.
|
||||
*/
|
||||
private predicate case2bForall(
|
||||
ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c, Splits except
|
||||
ControlFlowElement pred, Splits predSplits, ControlFlowElement succ, Completion c,
|
||||
Splits except
|
||||
) {
|
||||
// Invariant 1
|
||||
case2aux(pred, predSplits, succ, c) and
|
||||
@@ -669,12 +806,12 @@ private module SuccSplits {
|
||||
or
|
||||
case2(pred, predSplits, succ, succSplits, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import SuccSplits
|
||||
import SuccSplits
|
||||
|
||||
/** Provides logic for calculating reachable control flow nodes. */
|
||||
private module Reachability {
|
||||
/** Provides logic for calculating reachable control flow nodes. */
|
||||
private module Reachability {
|
||||
/**
|
||||
* Holds if `cfe` is a control flow element where the set of possible splits may
|
||||
* be different from the set of possible splits for one of `cfe`'s predecessors.
|
||||
@@ -703,11 +840,13 @@ private module Reachability {
|
||||
private predicate splitsBlockContains(ControlFlowElement start, ControlFlowElement cfe) =
|
||||
fastTC(intraSplitsSucc/2)(start, cfe)
|
||||
|
||||
final private class ControlFlowElementFinal = ControlFlowElement;
|
||||
|
||||
/**
|
||||
* A block of control flow elements where the set of splits is guaranteed
|
||||
* to remain unchanged, represented by the first element in the block.
|
||||
*/
|
||||
class SameSplitsBlock extends ControlFlowElement {
|
||||
class SameSplitsBlock extends ControlFlowElementFinal {
|
||||
SameSplitsBlock() { startsSplits(this) }
|
||||
|
||||
/** Gets a control flow element in this block. */
|
||||
@@ -738,14 +877,13 @@ private module Reachability {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
* If needed, call this predicate from `ControlFlowGraphImplSpecific.qll` in order to
|
||||
* force a stage-dependency on the `ControlFlowGraphImplShared.qll` stage and thereby
|
||||
* collapsing the two stages.
|
||||
* If needed, call this predicate from in order to force a stage-dependency on this
|
||||
* cached stage.
|
||||
*/
|
||||
cached
|
||||
predicate forceCachingInSameStage() { any() }
|
||||
@@ -872,16 +1010,27 @@ private module Cached {
|
||||
or
|
||||
n = TElementNode(result, _, _)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import Cached
|
||||
import Cached
|
||||
|
||||
/**
|
||||
/** A control-flow node. */
|
||||
signature class NodeSig instanceof TCfgNode {
|
||||
/** Gets the location of this element. */
|
||||
Location getLocation();
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Import this module into a `.ql` file of `@kind graph` to render a CFG. The
|
||||
* graph is restricted to nodes from `RelevantNode`.
|
||||
*/
|
||||
module TestOutput {
|
||||
abstract class RelevantNode extends Node {
|
||||
module TestOutput<NodeSig Node> {
|
||||
final private class NodeFinal = Node;
|
||||
|
||||
abstract class RelevantNode extends NodeFinal {
|
||||
/**
|
||||
* Gets a string used to resolve ties in node and edge ordering.
|
||||
*/
|
||||
@@ -893,13 +1042,13 @@ module TestOutput {
|
||||
val =
|
||||
any(int i |
|
||||
n =
|
||||
rank[i](RelevantNode p, Location l |
|
||||
l = p.getLocation()
|
||||
rank[i](RelevantNode p, string filePath, int startLine, int startColumn, int endLine,
|
||||
int endColumn |
|
||||
p.getLocation().hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
p
|
||||
order by
|
||||
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
|
||||
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), p.toString(),
|
||||
filePath, startLine, startColumn, endLine, endColumn, p.toString(),
|
||||
p.getOrderDisambiguation()
|
||||
)
|
||||
).toString()
|
||||
@@ -919,22 +1068,22 @@ module TestOutput {
|
||||
val =
|
||||
any(int i |
|
||||
succ =
|
||||
rank[i](RelevantNode s, SuccessorType t, Location l |
|
||||
rank[i](RelevantNode s, SuccessorType t, string filePath, int startLine,
|
||||
int startColumn, int endLine, int endColumn |
|
||||
s = getASuccessor(pred, t) and
|
||||
l = s.getLocation()
|
||||
s.getLocation().hasLocationInfo(filePath, startLine, startColumn, endLine, endColumn)
|
||||
|
|
||||
s
|
||||
order by
|
||||
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
|
||||
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), t.toString(), s.toString(),
|
||||
filePath, startLine, startColumn, endLine, endColumn, t.toString(), s.toString(),
|
||||
s.getOrderDisambiguation()
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a set of splitting-related consistency queries. */
|
||||
module Consistency {
|
||||
/** Provides a set of consistency queries. */
|
||||
module Consistency<NodeSig Node> {
|
||||
query predicate nonUniqueSetRepresentation(Splits s1, Splits s2) {
|
||||
forex(Split s | s = s1.getASplit() | s = s2.getASplit()) and
|
||||
forex(Split s | s = s2.getASplit() | s = s1.getASplit()) and
|
||||
@@ -983,13 +1132,15 @@ module Consistency {
|
||||
not split.hasEntry(pred, succ, c)
|
||||
}
|
||||
|
||||
private class SimpleSuccessorType extends SuccessorType {
|
||||
final private class SuccessorTypeFinal = SuccessorType;
|
||||
|
||||
private class SimpleSuccessorType extends SuccessorTypeFinal {
|
||||
SimpleSuccessorType() {
|
||||
this = getAMatchingSuccessorType(any(Completion c | completionIsSimple(c)))
|
||||
}
|
||||
}
|
||||
|
||||
private class NormalSuccessorType extends SuccessorType {
|
||||
private class NormalSuccessorType extends SuccessorTypeFinal {
|
||||
NormalSuccessorType() {
|
||||
this = getAMatchingSuccessorType(any(Completion c | completionIsNormal(c)))
|
||||
}
|
||||
@@ -1024,4 +1175,5 @@ module Consistency {
|
||||
ord = sk.getListOrder() and
|
||||
strictcount(sk.getListOrder()) > 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,6 @@ name: codeql/controlflow
|
||||
version: 0.0.1-dev
|
||||
groups: shared
|
||||
library: true
|
||||
dependencies:
|
||||
codeql/util: ${workspace}
|
||||
warnOnImplicitThis: true
|
||||
|
||||
36
shared/util/codeql/util/Location.qll
Normal file
36
shared/util/codeql/util/Location.qll
Normal file
@@ -0,0 +1,36 @@
|
||||
/** Provides classes for working with locations. */
|
||||
|
||||
/**
|
||||
* A location as given by a file, a start line, a start column,
|
||||
* an end line, and an end column.
|
||||
*
|
||||
* For more information about locations see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
signature class LocationSig {
|
||||
/** Gets the 1-based line number (inclusive) where this location starts. */
|
||||
int getStartLine();
|
||||
|
||||
/** Gets the 1-based column number (inclusive) where this location starts. */
|
||||
int getStartColumn();
|
||||
|
||||
/** Gets the 1-based line number (inclusive) where this location ends. */
|
||||
int getEndLine();
|
||||
|
||||
/** Gets the 1-based column number (inclusive) where this location ends. */
|
||||
int getEndColumn();
|
||||
|
||||
/** Gets a textual representation of this location. */
|
||||
bindingset[this]
|
||||
string toString();
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startColumn` of line `startLine` to
|
||||
* column `endColumn` of line `endLine` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Providing locations in CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filePath, int startLine, int startColumn, int endLine, int endColumn
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user