Make shared CFG construction library a parameterized module

This commit is contained in:
Tom Hvitved
2023-06-19 10:00:17 +02:00
parent 5049aafdd8
commit 1988397f93
17 changed files with 1629 additions and 2485 deletions

View File

@@ -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": [
@@ -573,4 +572,4 @@
"python/ql/lib/semmle/python/security/internal/EncryptionKeySizes.qll",
"java/ql/lib/semmle/code/java/security/internal/EncryptionKeySizes.qll"
]
}
}

View File

@@ -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
)

View File

@@ -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() }

View File

@@ -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

View File

@@ -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 }

View File

@@ -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" }
}
}

View File

@@ -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()
}
/**

View File

@@ -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;

View File

@@ -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()
)
}
}

View File

@@ -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

View File

@@ -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
/**

View File

@@ -6,6 +6,7 @@ dbscheme: ruby.dbscheme
upgrades: upgrades
library: true
dependencies:
codeql/controlflow: ${workspace}
codeql/dataflow: ${workspace}
codeql/mad: ${workspace}
codeql/regex: ${workspace}

File diff suppressed because it is too large Load Diff

View File

@@ -2,4 +2,6 @@ name: codeql/controlflow
version: 0.0.1-dev
groups: shared
library: true
dependencies:
codeql/util: ${workspace}
warnOnImplicitThis: true

View 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
);
}