Skip to content

Commit e787d99

Browse files
committed
Resolve yield calls to blocks
1 parent 66b2c39 commit e787d99

6 files changed

Lines changed: 116 additions & 20 deletions

File tree

ql/src/codeql_ruby/controlflow/CfgNodes.qll

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,23 @@ module ExprNodes {
231231
final ExprCfgNode getRightOperand() { e.hasCfgChild(e.getRightOperand(), this, result) }
232232
}
233233

234+
private class BlockArgumentChildMapping extends ExprChildMapping, BlockArgument {
235+
override predicate relevantChild(Expr e) { e = this.getValue() }
236+
}
237+
238+
/** A control-flow node that wraps a `BlockArgument` AST expression. */
239+
class BlockArgumentCfgNode extends ExprCfgNode {
240+
override BlockArgumentChildMapping e;
241+
242+
final override BlockArgument getExpr() { result = ExprCfgNode.super.getExpr() }
243+
244+
/** Gets the value of this block argument. */
245+
final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) }
246+
}
247+
234248
private class CallExprChildMapping extends ExprChildMapping, Call {
235249
override predicate relevantChild(Expr e) {
236-
e = [this.getAnArgument(), this.(MethodCall).getReceiver()]
250+
e = [this.getAnArgument(), this.(MethodCall).getReceiver(), this.(MethodCall).getBlock()]
237251
}
238252
}
239253

@@ -248,6 +262,9 @@ module ExprNodes {
248262

249263
/** Gets the receiver of this call. */
250264
final ExprCfgNode getReceiver() { e.hasCfgChild(e.(MethodCall).getReceiver(), this, result) }
265+
266+
/** Gets the block of this call. */
267+
final ExprCfgNode getBlock() { e.hasCfgChild(e.(MethodCall).getBlock(), this, result) }
251268
}
252269

253270
private class CaseExprChildMapping extends ExprChildMapping, CaseExpr {

ql/src/codeql_ruby/dataflow/internal/DataFlowDispatch.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ class DataFlowCall extends CfgNodes::ExprNodes::CallCfgNode {
5353
)
5454
}
5555

56+
private Block yieldCall() {
57+
this.getExpr() instanceof YieldCall and
58+
exists(BlockParameterNode node |
59+
node = trackBlock(result) and
60+
node.getMethod() = this.getExpr().getEnclosingMethod()
61+
)
62+
}
63+
5664
pragma[nomagic]
5765
private predicate superCall(Module superClass, string method) {
5866
this.getExpr() instanceof SuperCall and
@@ -89,6 +97,8 @@ class DataFlowCall extends CfgNodes::ExprNodes::CallCfgNode {
8997
this.superCall(superClass, method) and
9098
result = lookupMethod(superClass, method)
9199
)
100+
or
101+
result = this.yieldCall()
92102
}
93103
}
94104

@@ -163,6 +173,16 @@ private DataFlow::LocalSourceNode trackInstance(Module tp) {
163173
result = trackInstance(tp, TypeTracker::end())
164174
}
165175

176+
private DataFlow::LocalSourceNode trackBlock(Block block, TypeTracker t) {
177+
t.start() and result.asExpr().getExpr() = block
178+
or
179+
exists(TypeTracker t2 | result = trackBlock(block, t2).track(t2, t))
180+
}
181+
182+
private DataFlow::LocalSourceNode trackBlock(Block block) {
183+
result = trackBlock(block, TypeTracker::end())
184+
}
185+
166186
private predicate singletonMethod(MethodBase method, Expr object) {
167187
object = method.(SingletonMethod).getObject()
168188
or

ql/src/codeql_ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,12 @@ private module Cached {
115115
TExprNode(CfgNodes::ExprCfgNode n) or
116116
TReturningNode(CfgNodes::ReturningCfgNode n) or
117117
TSsaDefinitionNode(Ssa::Definition def) or
118-
TParameterNode(Parameter p) or
118+
TNormalParameterNode(Parameter p) { not p instanceof BlockParameter } or
119+
TBlockParameterNode(MethodBase m) or
119120
TExprPostUpdateNode(CfgNodes::ExprCfgNode n) { n.getNode() instanceof Argument }
120121

122+
class TParameterNode = TNormalParameterNode or TBlockParameterNode;
123+
121124
/**
122125
* This is the local flow predicate that is used as a building block in global
123126
* data flow. It excludes SSA flow through instance fields, as flow through fields
@@ -136,6 +139,8 @@ private module Cached {
136139
or
137140
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::AssignExprCfgNode).getRhs()
138141
or
142+
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::BlockArgumentCfgNode).getValue()
143+
or
139144
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::StmtSequenceCfgNode).getLastStmt()
140145
or
141146
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ConditionalExprCfgNode).getBranch(_)
@@ -221,10 +226,10 @@ private module ParameterNodes {
221226
* The value of an explicit parameter at function entry, viewed as a node in a data
222227
* flow graph.
223228
*/
224-
class ExplicitParameterNode extends ParameterNodeImpl, TParameterNode {
229+
class ExplicitParameterNode extends ParameterNodeImpl, TNormalParameterNode {
225230
private Parameter parameter;
226231

227-
ExplicitParameterNode() { this = TParameterNode(parameter) }
232+
ExplicitParameterNode() { this = TNormalParameterNode(parameter) }
228233

229234
override Parameter getParameter() { result = parameter }
230235

@@ -236,6 +241,38 @@ private module ParameterNodes {
236241

237242
override string toStringImpl() { result = parameter.toString() }
238243
}
244+
245+
/**
246+
* The value of a block parameter at function entry, viewed as a node in a data
247+
* flow graph.
248+
*/
249+
class BlockParameterNode extends ParameterNodeImpl, TBlockParameterNode {
250+
private MethodBase method;
251+
252+
BlockParameterNode() { this = TBlockParameterNode(method) }
253+
254+
final MethodBase getMethod() { result = method }
255+
256+
override Parameter getParameter() {
257+
result = method.getAParameter() and result instanceof BlockParameter
258+
}
259+
260+
override predicate isParameterOf(Callable c, int i) { c = method and i = -2 }
261+
262+
override CfgScope getCfgScope() { result = method }
263+
264+
override Location getLocationImpl() {
265+
result = getParameter().getLocation()
266+
or
267+
not exists(getParameter()) and result = method.getLocation()
268+
}
269+
270+
override string toStringImpl() {
271+
result = getParameter().toString()
272+
or
273+
not exists(getParameter()) and result = "&block"
274+
}
275+
}
239276
}
240277

241278
import ParameterNodes
@@ -253,7 +290,10 @@ abstract class ArgumentNode extends Node {
253290
private module ArgumentNodes {
254291
/** A data-flow node that represents an explicit call argument. */
255292
class ExplicitArgumentNode extends ArgumentNode {
256-
ExplicitArgumentNode() { this.asExpr().getExpr() instanceof Argument }
293+
ExplicitArgumentNode() {
294+
this.asExpr().getExpr() instanceof Argument and
295+
not this.asExpr().getExpr() instanceof BlockArgument
296+
}
257297

258298
override predicate argumentOf(DataFlowCall call, int pos) {
259299
this.asExpr() = call.getReceiver() and
@@ -262,6 +302,27 @@ private module ArgumentNodes {
262302
this.asExpr() = call.getArgument(pos)
263303
}
264304
}
305+
306+
/** A data-flow node that represents a block argument. */
307+
class BlockArgumentNode extends ArgumentNode {
308+
BlockArgumentNode() {
309+
this.asExpr().getExpr() instanceof BlockArgument or
310+
exists(CfgNodes::ExprNodes::CallCfgNode c | c.getBlock() = this.asExpr())
311+
}
312+
313+
override predicate argumentOf(DataFlowCall call, int pos) {
314+
pos = -2 and
315+
(
316+
this.asExpr() = call.getBlock()
317+
or
318+
exists(CfgNodes::ExprCfgNode arg, int n |
319+
arg = call.getArgument(n) and
320+
this.asExpr() = arg and
321+
arg.getExpr() instanceof BlockArgument
322+
)
323+
)
324+
}
325+
}
265326
}
266327

267328
import ArgumentNodes

ql/src/codeql_ruby/dataflow/internal/DataFlowPublic.qll

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,14 @@ class ExprNode extends Node, TExprNode {
6060
* flow graph.
6161
*/
6262
class ParameterNode extends Node, TParameterNode {
63-
private Parameter p;
64-
65-
ParameterNode() { this = TParameterNode(p) }
66-
6763
/** Gets the parameter corresponding to this node, if any. */
68-
Parameter getParameter() { result = p }
64+
Parameter getParameter() { none() }
6965

7066
/**
7167
* Holds if this node is the parameter of callable `c` at the specified
7268
* (zero-based) position.
7369
*/
74-
predicate isParameterOf(Callable c, int i) { p = c.getParameter(i) }
70+
predicate isParameterOf(Callable c, int i) { none() }
7571
}
7672

7773
/**

ql/src/codeql_ruby/typetracking/TypeTrackerSpecific.qll

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ predicate jumpStep(Node nodeFrom, Node nodeTo) {
3131
string getPossibleContentName() { result = getSetterCallAttributeName(_) }
3232

3333
/** Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. */
34-
predicate callStep(
35-
DataFlowPrivate::ArgumentNode nodeFrom, DataFlowPrivate::ExplicitParameterNode nodeTo
36-
) {
34+
predicate callStep(DataFlowPrivate::ArgumentNode nodeFrom, DataFlowPublic::ParameterNode nodeTo) {
3735
exists(DataFlowDispatch::DataFlowCall call, DataFlowDispatch::DataFlowCallable callable, int i |
3836
call.getTarget() = callable and
3937
nodeFrom.argumentOf(call, i) and

ql/test/library-tests/modules/callgraph.expected

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,37 @@ getTarget
2121
| calls.rb:60:1:60:12 | call to instance_m | calls.rb:16:5:16:23 | instance_m |
2222
| calls.rb:63:5:63:16 | call to bit_length | calls.rb:78:5:78:23 | bit_length |
2323
| calls.rb:64:5:64:16 | call to bit_length | calls.rb:78:5:78:23 | bit_length |
24+
| calls.rb:68:5:68:11 | yield ... | calls.rb:74:16:74:29 | { ... } |
25+
| calls.rb:68:5:68:11 | yield ... | calls.rb:140:10:140:28 | { ... } |
2426
| calls.rb:72:11:72:18 | call to new | calls.rb:98:5:98:16 | new |
2527
| calls.rb:73:5:73:10 | ...[...] | calls.rb:102:5:102:15 | [] |
2628
| calls.rb:74:5:74:29 | call to call_block | calls.rb:67:1:69:3 | call_block |
2729
| calls.rb:74:22:74:27 | ...[...] | calls.rb:102:5:102:15 | [] |
2830
| calls.rb:97:5:97:18 | call to include | calls.rb:92:5:92:20 | include |
2931
| calls.rb:111:15:111:25 | call to length | calls.rb:107:3:107:17 | length |
32+
| calls.rb:112:9:112:24 | yield ... | calls.rb:128:23:128:62 | { ... } |
33+
| calls.rb:112:9:112:24 | yield ... | calls.rb:130:17:130:35 | { ... } |
34+
| calls.rb:112:9:112:24 | yield ... | calls.rb:132:17:132:40 | { ... } |
35+
| calls.rb:112:9:112:24 | yield ... | calls.rb:134:18:134:37 | { ... } |
3036
| calls.rb:112:18:112:24 | ...[...] | calls.rb:106:3:106:13 | [] |
37+
| calls.rb:119:5:119:20 | yield ... | calls.rb:122:7:122:30 | { ... } |
3138
| calls.rb:122:1:122:30 | call to funny | calls.rb:118:1:120:3 | funny |
3239
| calls.rb:122:13:122:29 | call to puts | calls.rb:87:5:87:17 | puts |
40+
| calls.rb:122:18:122:29 | call to capitalize | calls.rb:83:5:83:23 | capitalize |
3341
| calls.rb:124:1:124:14 | call to capitalize | calls.rb:83:5:83:23 | capitalize |
3442
| calls.rb:125:1:125:12 | call to bit_length | calls.rb:78:5:78:23 | bit_length |
3543
| calls.rb:126:1:126:5 | call to abs | calls.rb:79:5:79:16 | abs |
3644
| calls.rb:128:1:128:62 | call to foreach | calls.rb:109:3:115:5 | foreach |
3745
| calls.rb:128:32:128:61 | call to puts | calls.rb:87:5:87:17 | puts |
3846
| calls.rb:130:1:130:35 | call to foreach | calls.rb:109:3:115:5 | foreach |
47+
| calls.rb:130:23:130:34 | call to bit_length | calls.rb:78:5:78:23 | bit_length |
3948
| calls.rb:132:1:132:40 | call to foreach | calls.rb:109:3:115:5 | foreach |
4049
| calls.rb:132:23:132:39 | call to puts | calls.rb:87:5:87:17 | puts |
4150
| calls.rb:134:1:134:37 | call to foreach | calls.rb:109:3:115:5 | foreach |
4251
| calls.rb:134:27:134:36 | call to puts | calls.rb:87:5:87:17 | puts |
4352
| calls.rb:137:5:137:17 | call to call_block | calls.rb:67:1:69:3 | call_block |
4453
| calls.rb:140:1:140:28 | call to indirect | calls.rb:136:1:138:3 | indirect |
54+
| calls.rb:140:16:140:27 | call to bit_length | calls.rb:78:5:78:23 | bit_length |
4555
| calls.rb:159:1:159:5 | call to new | calls.rb:98:5:98:16 | new |
4656
| calls.rb:159:1:159:14 | call to s_method | calls.rb:144:5:146:7 | s_method |
4757
| calls.rb:160:1:160:5 | call to new | calls.rb:98:5:98:16 | new |
@@ -74,13 +84,7 @@ unresolvedCall
7484
| calls.rb:42:9:42:24 | call to singleton_m |
7585
| calls.rb:48:1:48:13 | call to singleton_m |
7686
| calls.rb:59:1:59:13 | call to singleton_m |
77-
| calls.rb:68:5:68:11 | yield ... |
78-
| calls.rb:112:9:112:24 | yield ... |
79-
| calls.rb:119:5:119:20 | yield ... |
80-
| calls.rb:122:18:122:29 | call to capitalize |
8187
| calls.rb:128:48:128:59 | call to capitalize |
82-
| calls.rb:130:23:130:34 | call to bit_length |
8388
| calls.rb:132:28:132:39 | call to capitalize |
8489
| calls.rb:134:32:134:36 | call to abs |
85-
| calls.rb:140:16:140:27 | call to bit_length |
8690
| calls.rb:145:9:145:17 | call to to_s |

0 commit comments

Comments
 (0)