Skip to content

Commit 37f5db5

Browse files
committed
Ruby: Reduce captureFlow(In|Out)
When there is flow in/out of a block through a captured variable, we can restrict the calls that give rise to the flow to the method calls to which the blocks belong.
1 parent aa1284a commit 37f5db5

File tree

5 files changed

+106
-57
lines changed

5 files changed

+106
-57
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class EntryNode extends CfgNode, TEntryNode {
1414

1515
EntryNode() { this = TEntryNode(scope) }
1616

17-
final override EntryBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() }
17+
final override EntryBasicBlock getBasicBlock() { result = super.getBasicBlock() }
1818

1919
final override Location getLocation() { result = scope.getLocation() }
2020

@@ -31,7 +31,7 @@ class AnnotatedExitNode extends CfgNode, TAnnotatedExitNode {
3131
/** Holds if this node represent a normal exit. */
3232
final predicate isNormal() { normal = true }
3333

34-
final override AnnotatedExitBasicBlock getBasicBlock() { result = CfgNode.super.getBasicBlock() }
34+
final override AnnotatedExitBasicBlock getBasicBlock() { result = super.getBasicBlock() }
3535

3636
final override Location getLocation() { result = scope.getLocation() }
3737

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ module Ssa {
316316
CapturedCallDefinition() {
317317
exists(Variable v, BasicBlock bb, int i |
318318
this.definesAt(v, bb, i) and
319-
SsaImpl::capturedCallWrite(bb, i, v)
319+
SsaImpl::capturedCallWrite(_, bb, i, v)
320320
)
321321
}
322322

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

Lines changed: 102 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,21 @@ private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVaria
4141
i = bb.length()
4242
}
4343

44-
private CfgScope getCaptureOuterCfgScope(CfgScope scope) {
45-
result = scope.getOuterCfgScope() and
46-
(
47-
scope instanceof Block
48-
or
49-
scope instanceof Lambda
50-
)
51-
}
52-
53-
/** Holds if captured variable `v` is read inside `scope`. */
44+
/**
45+
* Holds if captured variable `v` is read directly inside `scope`,
46+
* or inside a (transitively) nested scope of `scope`.
47+
*/
5448
pragma[noinline]
5549
private predicate hasCapturedRead(Variable v, CfgScope scope) {
5650
any(LocalVariableReadAccess read |
57-
read.getVariable() = v and scope = getCaptureOuterCfgScope*(read.getCfgScope())
51+
read.getVariable() = v and scope = read.getCfgScope().getOuterCfgScope*()
5852
).isCapturedAccess()
5953
}
6054

55+
/**
56+
* Holds if `v` is written inside basic block `bb`, which is in the immediate
57+
* outer scope of `scope`.
58+
*/
6159
pragma[noinline]
6260
private predicate variableWriteInOuterScope(BasicBlock bb, LocalVariable v, CfgScope scope) {
6361
SsaImplSpecific::variableWrite(bb, _, v, _) and
@@ -71,30 +69,22 @@ private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable
7169
}
7270

7371
/**
74-
* Holds if the call at index `i` in basic block `bb` may reach a callable
75-
* that reads captured variable `v`.
72+
* Holds if the call `call` at index `i` in basic block `bb` may reach
73+
* a callable that reads captured variable `v`.
7674
*/
77-
private predicate capturedCallRead(BasicBlock bb, int i, LocalVariable v) {
75+
private predicate capturedCallRead(Call call, BasicBlock bb, int i, LocalVariable v) {
7876
exists(CfgScope scope |
7977
hasVariableWriteWithCapturedRead(bb.getAPredecessor*(), v, scope) and
80-
bb.getNode(i).getNode() instanceof Call
78+
call = bb.getNode(i).getNode()
8179
|
82-
not scope instanceof Block
83-
or
8480
// If the read happens inside a block, we restrict to the call that
8581
// contains the block
86-
scope = any(MethodCall c | bb.getNode(i) = c.getAControlFlowNode()).getBlock()
82+
not scope instanceof Block
83+
or
84+
scope = call.(MethodCall).getBlock()
8785
)
8886
}
8987

90-
/** Holds if captured variable `v` is written inside `scope`. */
91-
pragma[noinline]
92-
private predicate hasCapturedWrite(Variable v, CfgScope scope) {
93-
any(LocalVariableWriteAccess write |
94-
write.getVariable() = v and scope = getCaptureOuterCfgScope*(write.getCfgScope())
95-
).isCapturedAccess()
96-
}
97-
9888
/** Holds if `v` is read at index `i` in basic block `bb`. */
9989
private predicate variableReadActual(BasicBlock bb, int i, LocalVariable v) {
10090
exists(VariableReadAccess read |
@@ -107,21 +97,38 @@ predicate variableRead(BasicBlock bb, int i, LocalVariable v, boolean certain) {
10797
variableReadActual(bb, i, v) and
10898
certain = true
10999
or
110-
capturedCallRead(bb, i, v) and
100+
capturedCallRead(_, bb, i, v) and
111101
certain = false
112102
or
113103
capturedExitRead(bb, i, v) and
114104
certain = false
115105
}
116106

107+
/**
108+
* Holds if captured variable `v` is written directly inside `scope`,
109+
* or inside a (transitively) nested scope of `scope`.
110+
*/
111+
pragma[noinline]
112+
private predicate hasCapturedWrite(Variable v, CfgScope scope) {
113+
any(LocalVariableWriteAccess write |
114+
write.getVariable() = v and scope = write.getCfgScope().getOuterCfgScope*()
115+
).isCapturedAccess()
116+
}
117+
118+
/**
119+
* Holds if `v` is read inside basic block `bb`, which is in the immediate
120+
* outer scope of `scope`.
121+
*/
122+
pragma[noinline]
123+
private predicate variableReadActualInOuterScope(BasicBlock bb, LocalVariable v, CfgScope scope) {
124+
variableReadActual(bb, _, v) and
125+
bb.getScope() = scope.getOuterCfgScope()
126+
}
127+
117128
pragma[noinline]
118129
private predicate hasVariableReadWithCapturedWrite(BasicBlock bb, LocalVariable v, CfgScope scope) {
119130
hasCapturedWrite(v, scope) and
120-
exists(VariableReadAccess read |
121-
read = bb.getANode().getNode() and
122-
read.getVariable() = v and
123-
bb.getScope() = scope.getOuterCfgScope()
124-
)
131+
variableReadActualInOuterScope(bb, v, scope)
125132
}
126133

127134
cached
@@ -137,20 +144,20 @@ private module Cached {
137144
}
138145

139146
/**
140-
* Holds if the call at index `i` in basic block `bb` may reach a callable
147+
* Holds if the call `call` at index `i` in basic block `bb` may reach a callable
141148
* that writes captured variable `v`.
142149
*/
143150
cached
144-
predicate capturedCallWrite(BasicBlock bb, int i, LocalVariable v) {
151+
predicate capturedCallWrite(Call call, BasicBlock bb, int i, LocalVariable v) {
145152
exists(CfgScope scope |
146153
hasVariableReadWithCapturedWrite(bb.getASuccessor*(), v, scope) and
147-
bb.getNode(i).getNode() instanceof Call
154+
call = bb.getNode(i).getNode()
148155
|
149-
not scope instanceof Block
150-
or
151156
// If the write happens inside a block, we restrict to the call that
152157
// contains the block
153-
scope = any(MethodCall c | bb.getNode(i) = c.getAControlFlowNode()).getBlock()
158+
not scope instanceof Block
159+
or
160+
scope = call.(MethodCall).getBlock()
154161
)
155162
}
156163

@@ -180,6 +187,26 @@ private module Cached {
180187
)
181188
}
182189

190+
pragma[noinline]
191+
private predicate defReachesCallReadInOuterScope(
192+
Definition def, Call call, LocalVariable v, CfgScope scope
193+
) {
194+
exists(BasicBlock bb, int i |
195+
ssaDefReachesRead(v, def, bb, i) and
196+
capturedCallRead(call, bb, i, v) and
197+
scope.getOuterCfgScope() = bb.getScope()
198+
)
199+
}
200+
201+
pragma[noinline]
202+
private predicate hasCapturedEntryWrite(Definition entry, LocalVariable v, CfgScope scope) {
203+
exists(BasicBlock bb, int i |
204+
capturedEntryWrite(bb, i, v) and
205+
entry.definesAt(v, bb, i) and
206+
bb.getScope().getOuterCfgScope*() = scope
207+
)
208+
}
209+
183210
/**
184211
* Holds if there is flow for a captured variable from the enclosing scope into a block.
185212
* ```rb
@@ -191,13 +218,35 @@ private module Cached {
191218
*/
192219
cached
193220
predicate captureFlowIn(Definition def, Definition entry) {
194-
exists(LocalVariable v, BasicBlock bb, int i |
221+
exists(Call call, LocalVariable v, CfgScope scope |
222+
defReachesCallReadInOuterScope(def, call, v, scope) and
223+
hasCapturedEntryWrite(entry, v, scope)
224+
|
225+
// If the read happens inside a block, we restrict to the call that
226+
// contains the block
227+
not scope instanceof Block
228+
or
229+
scope = call.(MethodCall).getBlock()
230+
)
231+
}
232+
233+
private import codeql.ruby.dataflow.SSA
234+
235+
pragma[noinline]
236+
private predicate defReachesExitReadInInnerScope(Definition def, LocalVariable v, CfgScope scope) {
237+
exists(BasicBlock bb, int i |
195238
ssaDefReachesRead(v, def, bb, i) and
196-
capturedCallRead(bb, i, v) and
197-
exists(BasicBlock bb2, int i2 |
198-
capturedEntryWrite(bb2, i2, v) and
199-
entry.definesAt(v, bb2, i2)
200-
)
239+
capturedExitRead(bb, i, v) and
240+
scope = bb.getScope().getOuterCfgScope*()
241+
)
242+
}
243+
244+
pragma[noinline]
245+
private predicate hasCapturedExitRead(Definition exit, Call call, LocalVariable v, CfgScope scope) {
246+
exists(BasicBlock bb, int i |
247+
capturedCallWrite(call, bb, i, v) and
248+
exit.definesAt(v, bb, i) and
249+
bb.getScope() = scope.getOuterCfgScope()
201250
)
202251
}
203252

@@ -213,13 +262,15 @@ private module Cached {
213262
*/
214263
cached
215264
predicate captureFlowOut(Definition def, Definition exit) {
216-
exists(LocalVariable v, BasicBlock bb, int i |
217-
ssaDefReachesRead(v, def, bb, i) and
218-
capturedExitRead(bb, i, v) and
219-
exists(BasicBlock bb2, int i2 |
220-
capturedCallWrite(bb2, i2, v) and
221-
exit.definesAt(v, bb2, i2)
222-
)
265+
exists(Call call, LocalVariable v, CfgScope scope |
266+
defReachesExitReadInInnerScope(def, v, scope) and
267+
hasCapturedExitRead(exit, call, v, _)
268+
|
269+
// If the read happens inside a block, we restrict to the call that
270+
// contains the block
271+
not scope instanceof Block
272+
or
273+
scope = call.(MethodCall).getBlock()
223274
)
224275
}
225276

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain)
4040
) and
4141
certain = true
4242
or
43-
SsaImpl::capturedCallWrite(bb, i, v) and
43+
SsaImpl::capturedCallWrite(_, bb, i, v) and
4444
certain = false
4545
}
4646

ruby/ql/test/library-tests/dataflow/string-flow/string-flow.expected

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,6 @@ edges
201201
| string_flow.rb:241:10:241:10 | b [array element] : | string_flow.rb:241:10:241:13 | ...[...] |
202202
| string_flow.rb:242:10:242:10 | b [array element] : | string_flow.rb:242:10:242:13 | ...[...] |
203203
| string_flow.rb:246:5:246:18 | ... = ... : | string_flow.rb:250:26:250:26 | a : |
204-
| string_flow.rb:246:5:246:18 | ... = ... : | string_flow.rb:258:27:258:27 | a : |
205204
| string_flow.rb:246:9:246:18 | call to source : | string_flow.rb:246:5:246:18 | ... = ... : |
206205
| string_flow.rb:246:9:246:18 | call to source : | string_flow.rb:247:10:247:10 | a : |
207206
| string_flow.rb:246:9:246:18 | call to source : | string_flow.rb:248:20:248:20 | a : |
@@ -215,7 +214,6 @@ edges
215214
| string_flow.rb:250:26:250:26 | a : | string_flow.rb:250:10:250:28 | call to scrub |
216215
| string_flow.rb:252:10:252:10 | a : | string_flow.rb:252:10:252:22 | call to scrub! |
217216
| string_flow.rb:253:21:253:21 | a : | string_flow.rb:253:10:253:22 | call to scrub! |
218-
| string_flow.rb:255:5:255:18 | ... = ... : | string_flow.rb:250:26:250:26 | a : |
219217
| string_flow.rb:255:5:255:18 | ... = ... : | string_flow.rb:258:27:258:27 | a : |
220218
| string_flow.rb:255:9:255:18 | call to source : | string_flow.rb:255:5:255:18 | ... = ... : |
221219
| string_flow.rb:255:9:255:18 | call to source : | string_flow.rb:256:5:256:5 | a : |

0 commit comments

Comments
 (0)