Skip to content

Commit bb85f87

Browse files
authored
Merge pull request #11725 from hvitved/ruby/capture-field-flow
Ruby: Reimplement flow through captured variables using field flow
2 parents 3bf0d66 + 88d2e25 commit bb85f87

25 files changed

+1121
-425
lines changed

ruby/ql/consistency-queries/DataFlowConsistency.ql

-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ private module Input implements InputSig<RubyDataFlow> {
1111
predicate postWithInFlowExclude(Node n) { n instanceof FlowSummaryNode }
1212

1313
predicate argHasPostUpdateExclude(ArgumentNode n) {
14-
n instanceof BlockArgumentNode
15-
or
1614
n instanceof FlowSummaryNode
1715
or
1816
n instanceof SynthHashSplatArgumentNode

ruby/ql/consistency-queries/VariablesConsistency.ql

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import codeql.ruby.ast.Variable
2+
import codeql.ruby.dataflow.internal.DataFlowPrivate::VariableCapture::Flow::ConsistencyChecks
23

34
query predicate ambiguousVariable(VariableAccess access, Variable variable) {
45
access.getVariable() = variable and
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: majorAnalysis
3+
---
4+
* Improved support for flow through captured variables that properly adheres to inter-procedural control flow.

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

+11
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ class MethodCall extends Call instanceof MethodCallImpl {
117117
*/
118118
final Block getBlock() { result = super.getBlockImpl() }
119119

120+
/**
121+
* Gets the block argument of this method call, if any.
122+
* ```rb
123+
* foo(&block)
124+
* ```
125+
*/
126+
final BlockArgument getBlockArgument() { result = this.getAnArgument() }
127+
128+
/** Holds if this method call has a block or block argument. */
129+
final predicate hasBlock() { exists(this.getBlock()) or exists(this.getBlockArgument()) }
130+
120131
/**
121132
* Holds if the safe navigation operator (`&.`) is used in this call.
122133
* ```rb

ruby/ql/lib/codeql/ruby/controlflow/CfgNodes.qll

+9-2
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,15 @@ module ExprNodes {
201201

202202
override LhsExpr getExpr() { result = super.getExpr() }
203203

204-
/** Gets a variable used in (or introduced by) this LHS. */
205-
Variable getAVariable() { result = e.(VariableAccess).getVariable() }
204+
/**
205+
* DEPRECATED: use `getVariable` instead.
206+
*
207+
* Gets a variable used in (or introduced by) this LHS.
208+
*/
209+
deprecated Variable getAVariable() { result = e.(VariableAccess).getVariable() }
210+
211+
/** Gets the variable used in (or introduced by) this LHS. */
212+
Variable getVariable() { result = e.(VariableAccess).getVariable() }
206213
}
207214

208215
private class AssignExprChildMapping extends ExprChildMapping, AssignExpr {

ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll

+56
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/** Provides classes and predicates for defining flow summaries. */
22

33
import codeql.ruby.AST
4+
private import codeql.ruby.CFG
5+
private import codeql.ruby.typetracking.TypeTracker
46
import codeql.ruby.DataFlow
57
private import internal.FlowSummaryImpl as Impl
68
private import internal.DataFlowDispatch
@@ -158,3 +160,57 @@ abstract class SimpleSummarizedCallable extends SummarizedCallable {
158160
}
159161

160162
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
163+
164+
/**
165+
* Provides a set of special flow summaries to ensure that callbacks passed into
166+
* library methods will be passed as `lambda-self` arguments into themselves. That is,
167+
* we are assuming that callbacks passed into library methods will be called, which is
168+
* needed for flow through captured variables.
169+
*/
170+
private module LibraryCallbackSummaries {
171+
private predicate libraryCall(CfgNodes::ExprNodes::CallCfgNode call) {
172+
not exists(getTarget(call))
173+
}
174+
175+
private DataFlow::LocalSourceNode trackLambdaCreation(TypeTracker t) {
176+
t.start() and
177+
lambdaCreation(result, TLambdaCallKind(), _)
178+
or
179+
exists(TypeTracker t2 | result = trackLambdaCreation(t2).track(t2, t)) and
180+
not result instanceof DataFlow::SelfParameterNode
181+
}
182+
183+
private predicate libraryCallHasLambdaArg(CfgNodes::ExprNodes::CallCfgNode call, int i) {
184+
exists(CfgNodes::ExprCfgNode arg |
185+
arg = call.getArgument(i) and
186+
arg = trackLambdaCreation(TypeTracker::end()).getALocalUse().asExpr() and
187+
libraryCall(call) and
188+
not arg instanceof CfgNodes::ExprNodes::BlockArgumentCfgNode
189+
)
190+
}
191+
192+
private class LibraryLambdaMethod extends SummarizedCallable {
193+
LibraryLambdaMethod() { this = "<library method accepting a callback>" }
194+
195+
final override MethodCall getACall() {
196+
libraryCall(result.getAControlFlowNode()) and
197+
result.hasBlock()
198+
or
199+
libraryCallHasLambdaArg(result.getAControlFlowNode(), _)
200+
}
201+
202+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
203+
(
204+
input = "Argument[block]" and
205+
output = "Argument[block].Parameter[lambda-self]"
206+
or
207+
exists(int i |
208+
i in [0 .. 10] and
209+
input = "Argument[" + i + "]" and
210+
output = "Argument[" + i + "].Parameter[lambda-self]"
211+
)
212+
) and
213+
preservesValue = true
214+
}
215+
}
216+
}

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll

+34-13
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ private class SelfLocalSourceNode extends DataFlow::LocalSourceNode {
2121
SelfLocalSourceNode() {
2222
self = this.(SelfParameterNodeImpl).getSelfVariable()
2323
or
24-
self = this.(SsaSelfDefinitionNode).getVariable() and
25-
not LocalFlow::localFlowSsaParamInput(_, this)
24+
self = this.(SsaSelfDefinitionNode).getVariable()
2625
}
2726

2827
/** Gets the `self` variable. */
@@ -424,6 +423,7 @@ private module Cached {
424423
cached
425424
newtype TArgumentPosition =
426425
TSelfArgumentPosition() or
426+
TLambdaSelfArgumentPosition() or
427427
TBlockArgumentPosition() or
428428
TPositionalArgumentPosition(int pos) {
429429
exists(Call c | exists(c.getArgument(pos)))
@@ -446,6 +446,7 @@ private module Cached {
446446
cached
447447
newtype TParameterPosition =
448448
TSelfParameterPosition() or
449+
TLambdaSelfParameterPosition() or
449450
TBlockParameterPosition() or
450451
TPositionalParameterPosition(int pos) {
451452
pos = any(Parameter p).getPosition()
@@ -941,20 +942,24 @@ private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruct
941942
private predicate paramReturnFlow(
942943
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
943944
) {
944-
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
945+
exists(
946+
RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p,
947+
CfgNodes::ExprCfgNode nodeFromPreExpr
948+
|
945949
TypeTrackerSpecific::callStep(call, arg, p) and
946950
nodeTo.getPreUpdateNode() = arg and
947951
summary.toString() = "return" and
948952
(
949-
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
953+
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()
950954
or
951-
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
952-
singletonMethodOnInstance(_, _, nodeFromPreExpr)
955+
nodeFromPreExpr = nodeFrom.asExpr() and
956+
singletonMethodOnInstance(_, _, nodeFromPreExpr.getExpr())
953957
)
954958
|
955-
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
959+
nodeFromPreExpr =
960+
LocalFlow::getParameterDefNode(p.getParameter()).getDefinitionExt().getARead()
956961
or
957-
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfVariable().getAnAccess()
962+
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfDefinition().getARead()
958963
)
959964
}
960965

@@ -1276,6 +1281,9 @@ class ParameterPosition extends TParameterPosition {
12761281
/** Holds if this position represents a `self` parameter. */
12771282
predicate isSelf() { this = TSelfParameterPosition() }
12781283

1284+
/** Holds if this position represents a reference to a lambda itself. Only used for tracking flow through captured variables. */
1285+
predicate isLambdaSelf() { this = TLambdaSelfParameterPosition() }
1286+
12791287
/** Holds if this position represents a block parameter. */
12801288
predicate isBlock() { this = TBlockParameterPosition() }
12811289

@@ -1313,6 +1321,8 @@ class ParameterPosition extends TParameterPosition {
13131321
string toString() {
13141322
this.isSelf() and result = "self"
13151323
or
1324+
this.isLambdaSelf() and result = "lambda self"
1325+
or
13161326
this.isBlock() and result = "block"
13171327
or
13181328
exists(int pos | this.isPositional(pos) and result = "position " + pos)
@@ -1342,6 +1352,9 @@ class ArgumentPosition extends TArgumentPosition {
13421352
/** Holds if this position represents a `self` argument. */
13431353
predicate isSelf() { this = TSelfArgumentPosition() }
13441354

1355+
/** Holds if this position represents a lambda `self` argument. Only used for tracking flow through captured variables. */
1356+
predicate isLambdaSelf() { this = TLambdaSelfArgumentPosition() }
1357+
13451358
/** Holds if this position represents a block argument. */
13461359
predicate isBlock() { this = TBlockArgumentPosition() }
13471360

@@ -1374,6 +1387,8 @@ class ArgumentPosition extends TArgumentPosition {
13741387
string toString() {
13751388
this.isSelf() and result = "self"
13761389
or
1390+
this.isLambdaSelf() and result = "lambda self"
1391+
or
13771392
this.isBlock() and result = "block"
13781393
or
13791394
exists(int pos | this.isPositional(pos) and result = "position " + pos)
@@ -1393,16 +1408,24 @@ class ArgumentPosition extends TArgumentPosition {
13931408
}
13941409

13951410
pragma[nomagic]
1396-
private predicate parameterPositionIsNotSelf(ParameterPosition ppos) { not ppos.isSelf() }
1411+
private predicate parameterPositionIsNotSelf(ParameterPosition ppos) {
1412+
not ppos.isSelf() and
1413+
not ppos.isLambdaSelf()
1414+
}
13971415

13981416
pragma[nomagic]
1399-
private predicate argumentPositionIsNotSelf(ArgumentPosition apos) { not apos.isSelf() }
1417+
private predicate argumentPositionIsNotSelf(ArgumentPosition apos) {
1418+
not apos.isSelf() and
1419+
not apos.isLambdaSelf()
1420+
}
14001421

14011422
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
14021423
pragma[nomagic]
14031424
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
14041425
ppos.isSelf() and apos.isSelf()
14051426
or
1427+
ppos.isLambdaSelf() and apos.isLambdaSelf()
1428+
or
14061429
ppos.isBlock() and apos.isBlock()
14071430
or
14081431
exists(int pos | ppos.isPositional(pos) and apos.isPositional(pos))
@@ -1441,8 +1464,6 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
14411464
* This is a temporary hook to support technical debt in the Go language; do not use.
14421465
*/
14431466
pragma[inline]
1444-
predicate golangSpecificParamArgFilter(
1445-
DataFlowCall call, DataFlow::ParameterNode p, ArgumentNode arg
1446-
) {
1467+
predicate golangSpecificParamArgFilter(DataFlowCall call, ParameterNodeImpl p, ArgumentNode arg) {
14471468
any()
14481469
}

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImplSpecific.qll

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ module RubyDataFlow implements InputSig {
1717
import Private
1818
import Public
1919

20+
// includes `LambdaSelfReferenceNode`, which is not part of the public API
21+
class ParameterNode = Private::ParameterNodeImpl;
22+
2023
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
2124
Private::isParameterNode(p, c, pos)
2225
}

0 commit comments

Comments
 (0)