Skip to content

Commit 093a387

Browse files
authored
Merge pull request #8794 from hvitved/ruby/capture-barrier-guards
Ruby: Handle captured variables in `BarrierGuard::getAGuardedNode()`
2 parents bf92117 + addb92f commit 093a387

File tree

5 files changed

+70
-17
lines changed

5 files changed

+70
-17
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -767,10 +767,10 @@ private module OutNodes {
767767
import OutNodes
768768

769769
predicate jumpStep(Node pred, Node succ) {
770-
SsaImpl::captureFlowIn(pred.(SsaDefinitionNode).getDefinition(),
770+
SsaImpl::captureFlowIn(_, pred.(SsaDefinitionNode).getDefinition(),
771771
succ.(SsaDefinitionNode).getDefinition())
772772
or
773-
SsaImpl::captureFlowOut(pred.(SsaDefinitionNode).getDefinition(),
773+
SsaImpl::captureFlowOut(_, pred.(SsaDefinitionNode).getDefinition(),
774774
succ.(SsaDefinitionNode).getDefinition())
775775
or
776776
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ private import codeql.ruby.CFG
55
private import codeql.ruby.typetracking.TypeTracker
66
private import codeql.ruby.dataflow.SSA
77
private import FlowSummaryImpl as FlowSummaryImpl
8+
private import SsaImpl as SsaImpl
89

910
/**
1011
* An element, viewed as a node in a data flow graph. Either an expression
@@ -256,12 +257,34 @@ abstract class BarrierGuard extends CfgNodes::ExprCfgNode {
256257
*/
257258
abstract predicate checks(CfgNode expr, boolean branch);
258259

260+
/**
261+
* Gets an implicit entry definition for a captured variable that
262+
* may be guarded, because a call to the capturing callable is guarded.
263+
*
264+
* This is restricted to calls where the variable is captured inside a
265+
* block.
266+
*/
267+
private Ssa::Definition getAMaybeGuardedCapturedDef() {
268+
exists(
269+
boolean branch, CfgNodes::ExprCfgNode testedNode, Ssa::Definition def,
270+
CfgNodes::ExprNodes::CallCfgNode call
271+
|
272+
def.getARead() = testedNode and
273+
this.checks(testedNode, branch) and
274+
SsaImpl::captureFlowIn(call, def, result) and
275+
this.controlsBlock(call.getBasicBlock(), branch) and
276+
result.getBasicBlock().getScope() = call.getExpr().(MethodCall).getBlock()
277+
)
278+
}
279+
259280
final Node getAGuardedNode() {
260281
exists(boolean branch, CfgNodes::ExprCfgNode testedNode, Ssa::Definition def |
261282
def.getARead() = testedNode and
262283
def.getARead() = result.asExpr() and
263284
this.checks(testedNode, branch) and
264285
this.controlsBlock(result.asExpr().getBasicBlock(), branch)
265286
)
287+
or
288+
result.asExpr() = this.getAMaybeGuardedCapturedDef().getARead()
266289
}
267290
}

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

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,18 @@ private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable
7272
* Holds if the call `call` at index `i` in basic block `bb` may reach
7373
* a callable that reads captured variable `v`.
7474
*/
75-
private predicate capturedCallRead(Call call, BasicBlock bb, int i, LocalVariable v) {
75+
private predicate capturedCallRead(
76+
CfgNodes::ExprNodes::CallCfgNode call, BasicBlock bb, int i, LocalVariable v
77+
) {
7678
exists(CfgScope scope |
7779
hasVariableWriteWithCapturedRead(bb.getAPredecessor*(), v, scope) and
78-
call = bb.getNode(i).getNode()
80+
call = bb.getNode(i)
7981
|
8082
// If the read happens inside a block, we restrict to the call that
8183
// contains the block
8284
not scope instanceof Block
8385
or
84-
scope = call.(MethodCall).getBlock()
86+
scope = call.getExpr().(MethodCall).getBlock()
8587
)
8688
}
8789

@@ -148,16 +150,18 @@ private module Cached {
148150
* that writes captured variable `v`.
149151
*/
150152
cached
151-
predicate capturedCallWrite(Call call, BasicBlock bb, int i, LocalVariable v) {
153+
predicate capturedCallWrite(
154+
CfgNodes::ExprNodes::CallCfgNode call, BasicBlock bb, int i, LocalVariable v
155+
) {
152156
exists(CfgScope scope |
153157
hasVariableReadWithCapturedWrite(bb.getASuccessor*(), v, scope) and
154-
call = bb.getNode(i).getNode()
158+
call = bb.getNode(i)
155159
|
156160
// If the write happens inside a block, we restrict to the call that
157161
// contains the block
158162
not scope instanceof Block
159163
or
160-
scope = call.(MethodCall).getBlock()
164+
scope = call.getExpr().(MethodCall).getBlock()
161165
)
162166
}
163167

@@ -189,7 +193,7 @@ private module Cached {
189193

190194
pragma[noinline]
191195
private predicate defReachesCallReadInOuterScope(
192-
Definition def, Call call, LocalVariable v, CfgScope scope
196+
Definition def, CfgNodes::ExprNodes::CallCfgNode call, LocalVariable v, CfgScope scope
193197
) {
194198
exists(BasicBlock bb, int i |
195199
ssaDefReachesRead(v, def, bb, i) and
@@ -217,16 +221,16 @@ private module Cached {
217221
* ```
218222
*/
219223
cached
220-
predicate captureFlowIn(Definition def, Definition entry) {
221-
exists(Call call, LocalVariable v, CfgScope scope |
224+
predicate captureFlowIn(CfgNodes::ExprNodes::CallCfgNode call, Definition def, Definition entry) {
225+
exists(LocalVariable v, CfgScope scope |
222226
defReachesCallReadInOuterScope(def, call, v, scope) and
223227
hasCapturedEntryWrite(entry, v, scope)
224228
|
225229
// If the read happens inside a block, we restrict to the call that
226230
// contains the block
227231
not scope instanceof Block
228232
or
229-
scope = call.(MethodCall).getBlock()
233+
scope = call.getExpr().(MethodCall).getBlock()
230234
)
231235
}
232236

@@ -242,7 +246,9 @@ private module Cached {
242246
}
243247

244248
pragma[noinline]
245-
private predicate hasCapturedExitRead(Definition exit, Call call, LocalVariable v, CfgScope scope) {
249+
private predicate hasCapturedExitRead(
250+
Definition exit, CfgNodes::ExprNodes::CallCfgNode call, LocalVariable v, CfgScope scope
251+
) {
246252
exists(BasicBlock bb, int i |
247253
capturedCallWrite(call, bb, i, v) and
248254
exit.definesAt(v, bb, i) and
@@ -261,16 +267,16 @@ private module Cached {
261267
* ```
262268
*/
263269
cached
264-
predicate captureFlowOut(Definition def, Definition exit) {
265-
exists(Call call, LocalVariable v, CfgScope scope |
270+
predicate captureFlowOut(CfgNodes::ExprNodes::CallCfgNode call, Definition def, Definition exit) {
271+
exists(LocalVariable v, CfgScope scope |
266272
defReachesExitReadInInnerScope(def, v, scope) and
267273
hasCapturedExitRead(exit, call, v, _)
268274
|
269275
// If the read happens inside a block, we restrict to the call that
270276
// contains the block
271277
not scope instanceof Block
272278
or
273-
scope = call.(MethodCall).getBlock()
279+
scope = call.getExpr().(MethodCall).getBlock()
274280
)
275281
}
276282

ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
| barrier-guards.rb:21:8:21:19 | ... == ... | barrier-guards.rb:24:5:24:7 | foo | barrier-guards.rb:21:8:21:10 | foo | true |
55
| barrier-guards.rb:27:8:27:19 | ... != ... | barrier-guards.rb:28:5:28:7 | foo | barrier-guards.rb:27:8:27:10 | foo | false |
66
| barrier-guards.rb:37:4:37:20 | call to include? | barrier-guards.rb:38:5:38:7 | foo | barrier-guards.rb:37:17:37:19 | foo | true |
7+
| barrier-guards.rb:43:4:43:15 | ... == ... | barrier-guards.rb:45:9:45:11 | foo | barrier-guards.rb:43:4:43:6 | foo | true |

ruby/ql/test/library-tests/dataflow/barrier-guards/barrier-guards.rb

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,27 @@
3838
foo
3939
else
4040
foo
41-
end
41+
end
42+
43+
if foo == "foo"
44+
capture {
45+
foo # guarded
46+
}
47+
end
48+
49+
if foo == "foo"
50+
capture {
51+
foo = "bar"
52+
foo # not guarded
53+
}
54+
end
55+
56+
if foo == "foo"
57+
my_lambda = -> () {
58+
foo # not guarded
59+
}
60+
61+
foo = "bar"
62+
63+
my_lambda()
64+
end

0 commit comments

Comments
 (0)