Skip to content

Commit daa37c0

Browse files
authored
Merge pull request #22158 from aschackmull/ruby/cfg-swap
Ruby: Replace CFG with shared implementation
2 parents 5027b36 + ac0438f commit daa37c0

45 files changed

Lines changed: 10440 additions & 12806 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,2 @@
1-
import codeql.ruby.controlflow.internal.ControlFlowGraphImpl::Consistency as Consistency
2-
import Consistency
3-
import codeql.ruby.AST
41
import codeql.ruby.CFG
5-
import codeql.ruby.controlflow.internal.Completion
6-
import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as CfgImpl
7-
8-
/**
9-
* All `Expr` nodes are `PostOrderTree`s
10-
*/
11-
query predicate nonPostOrderExpr(Expr e, string cls) {
12-
cls = e.getPrimaryQlClasses() and
13-
not exists(e.getDesugared()) and
14-
not e instanceof BodyStmt and
15-
exists(AstNode last, Completion c |
16-
CfgImpl::last(e, last, c) and
17-
last != e and
18-
c instanceof NormalCompletion
19-
)
20-
}
21-
22-
query predicate scopeNoFirst(CfgScope scope) {
23-
Consistency::scopeNoFirst(scope) and
24-
not scope = any(StmtSequence seq | not exists(seq.getAStmt())) and
25-
not scope =
26-
any(Callable c |
27-
not exists(c.getAParameter()) and
28-
not c.getBody().hasEnsure() and
29-
not exists(c.getBody().getARescue())
30-
)
31-
}
2+
import ControlFlow::Consistency

ruby/ql/consistency-queries/DataFlowConsistency.ql

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,6 @@ private module Input implements InputSig<Location, RubyDataFlow> {
2222
not isNonConstantExpr(n.asExpr())
2323
}
2424

25-
predicate multipleArgumentCallExclude(ArgumentNode arg, DataFlowCall call) {
26-
// An argument such as `x` in `if not x then ...` has two successors (and hence
27-
// two calls); one for each Boolean outcome of `x`.
28-
exists(CfgNodes::ExprCfgNode n |
29-
arg.argumentOf(call, _) and
30-
n = call.asCall() and
31-
arg.asExpr().getASuccessor(any(ConditionalSuccessor c)).getASuccessor*() = n and
32-
n.getASplit() instanceof Split::ConditionalCompletionSplit
33-
)
34-
}
35-
3625
predicate uniqueTypeExclude(Node n) {
3726
n =
3827
any(DataFlow::CallNode call |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
category: breaking
3+
---
4+
* The Ruby control flow graph implementation has been completely replaced. This
5+
affects a number of queries slightly. The CFG now includes additional nodes
6+
to more accurately represent certain constructs. This also means that any
7+
existing code that implicitly relies on very specific details about the CFG
8+
may need to be updated. The CFG no longer uses splitting, which means that
9+
AST nodes now have a unique CFG node representation. In particular,
10+
`ControlFlowNode.getAstNode` has changed its meaning. The AST-to-CFG mapping
11+
remains one-to-many, but now for a different reason. It used to be because of
12+
splitting, but now it's because of additional "helper" CFG nodes. To get the
13+
(now canonical) CFG node for a given AST node, use
14+
`Stmt.getControlFlowNode()` instead.

ruby/ql/lib/codeql/ruby/CFG.qll

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@
33
import codeql.Locations
44
import controlflow.ControlFlowGraph
55
import controlflow.CfgNodes as CfgNodes
6-
import controlflow.BasicBlocks

ruby/ql/lib/codeql/ruby/ast/Call.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class Call extends Expr instanceof CallImpl {
6363
TCfgScope(result) = viableCallableLambda(c, _)
6464
)
6565
or
66-
result = getTarget(TNormalCall(this.getAControlFlowNode()))
66+
result = getTarget(TNormalCall(this.getControlFlowNode()))
6767
}
6868

6969
override AstNode getAChild(string pred) {

ruby/ql/lib/codeql/ruby/ast/Statement.qll

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,25 @@ private import codeql.ruby.CFG
66
private import internal.AST
77
private import internal.TreeSitter
88
private import internal.Variable
9-
private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl as CfgImpl
109

1110
/**
1211
* A statement.
1312
*
1413
* This is the root QL class for all statements.
1514
*/
1615
class Stmt extends AstNode, TStmt {
17-
/** Gets a control-flow node for this statement, if any. */
18-
CfgNodes::AstCfgNode getAControlFlowNode() { result.getAstNode() = this }
16+
/** Gets the control-flow node for this statement, if any. */
17+
ControlFlowNode getControlFlowNode() { result.injects(this) }
1918

20-
/** Gets a control-flow entry node for this statement, if any */
21-
AstNode getAControlFlowEntryNode() { result = CfgImpl::getAControlFlowEntryNode(this) }
19+
/**
20+
* DEPRECATED: Use `getControlFlowNode()` instead.
21+
*
22+
* Gets a control-flow node for this statement, if any.
23+
*/
24+
deprecated CfgNodes::AstCfgNode getAControlFlowNode() { result.getAstNode() = this }
2225

2326
/** Gets the control-flow scope of this statement, if any. */
24-
CfgScope getCfgScope() { result = CfgImpl::getCfgScope(this) }
27+
CfgScope getCfgScope() { result = getEnclosingCallable(this) }
2528

2629
/** Gets the enclosing callable, if any. */
2730
Callable getEnclosingCallable() { result = this.getCfgScope() }

ruby/ql/lib/codeql/ruby/ast/internal/Constant.qll

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ private module Propagation {
9595
or
9696
isIntExpr(e.(ConstantReadAccess).getValue(), i)
9797
or
98-
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isInt(n, i))
98+
isInt(e.getControlFlowNode(), i)
9999
}
100100

101101
predicate isFloat(ExprCfgNode e, float f) {
@@ -153,7 +153,7 @@ private module Propagation {
153153
or
154154
isFloatExpr(e.(ConstantReadAccess).getValue(), f)
155155
or
156-
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isFloat(n, f))
156+
isFloat(e.getControlFlowNode(), f)
157157
}
158158

159159
predicate isRational(ExprCfgNode e, int numerator, int denominator) {
@@ -175,7 +175,7 @@ private module Propagation {
175175
or
176176
isRationalExpr(e.(ConstantReadAccess).getValue(), numerator, denominator)
177177
or
178-
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isRational(n, numerator, denominator))
178+
isRational(e.getControlFlowNode(), numerator, denominator)
179179
}
180180

181181
predicate isComplex(ExprCfgNode e, float real, float imaginary) {
@@ -197,7 +197,7 @@ private module Propagation {
197197
or
198198
isComplexExpr(e.(ConstantReadAccess).getValue(), real, imaginary)
199199
or
200-
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isComplex(n, real, imaginary))
200+
isComplex(e.getControlFlowNode(), real, imaginary)
201201
}
202202

203203
overlay[local]
@@ -309,7 +309,7 @@ private module Propagation {
309309
or
310310
isStringExpr(e.(ConstantReadAccess).getValue(), s)
311311
or
312-
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isString(n, s))
312+
isString(e.getControlFlowNode(), s)
313313
}
314314

315315
predicate isSymbol(ExprCfgNode e, string s) {
@@ -334,7 +334,7 @@ private module Propagation {
334334
or
335335
isSymbolExpr(e.(ConstantReadAccess).getValue(), s)
336336
or
337-
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isSymbol(n, s))
337+
isSymbol(e.getControlFlowNode(), s)
338338
}
339339

340340
predicate isRegExp(ExprCfgNode e, string s, string flags) {
@@ -359,7 +359,7 @@ private module Propagation {
359359
or
360360
isRegExpExpr(e.(ConstantReadAccess).getValue(), s, flags)
361361
or
362-
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isRegExp(n, s, flags))
362+
isRegExp(e.getControlFlowNode(), s, flags)
363363
}
364364

365365
predicate isBoolean(ExprCfgNode e, boolean b) {
@@ -381,7 +381,7 @@ private module Propagation {
381381
or
382382
isBooleanExpr(e.(ConstantReadAccess).getValue(), b)
383383
or
384-
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isBoolean(n, b))
384+
isBoolean(e.getControlFlowNode(), b)
385385
}
386386

387387
predicate isNil(ExprCfgNode e) {
@@ -403,7 +403,7 @@ private module Propagation {
403403
or
404404
isNilExpr(e.(ConstantReadAccess).getValue())
405405
or
406-
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isNil(n))
406+
isNil(e.getControlFlowNode())
407407
}
408408
}
409409

@@ -566,7 +566,7 @@ private predicate isArrayExpr(Expr e, ArrayLiteralCfgNode arr) {
566566
// control flow paths.
567567
// Note(hmac): I don't think this is necessary, as `getSource` will not return
568568
// results if the source is a phi node.
569-
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isArrayConstant(n, arr))
569+
isArrayConstant(e.getControlFlowNode(), arr)
570570
or
571571
// if `e` is an array, then `e.freeze` is also an array
572572
e.(MethodCall).getMethodName() = "freeze" and

ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,10 @@ private module AssignOperationDesugar {
833833
)
834834
)
835835
}
836+
837+
final override predicate excludeFromControlFlowTree(AstNode n) {
838+
n = any(ScopeResolutionAssignOperation sao).getLeftOperand()
839+
}
836840
}
837841

838842
/** An assignment operation where the left-hand side is a method call. */

0 commit comments

Comments
 (0)