@@ -41,23 +41,21 @@ private predicate capturedExitRead(AnnotatedExitBasicBlock bb, int i, LocalVaria
41
41
i = bb .length ( )
42
42
}
43
43
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
+ */
54
48
pragma [ noinline]
55
49
private predicate hasCapturedRead ( Variable v , CfgScope scope ) {
56
50
any ( LocalVariableReadAccess read |
57
- read .getVariable ( ) = v and scope = getCaptureOuterCfgScope * ( read .getCfgScope ( ) )
51
+ read .getVariable ( ) = v and scope = read .getCfgScope ( ) . getOuterCfgScope * ( )
58
52
) .isCapturedAccess ( )
59
53
}
60
54
55
+ /**
56
+ * Holds if `v` is written inside basic block `bb`, which is in the immediate
57
+ * outer scope of `scope`.
58
+ */
61
59
pragma [ noinline]
62
60
private predicate variableWriteInOuterScope ( BasicBlock bb , LocalVariable v , CfgScope scope ) {
63
61
SsaImplSpecific:: variableWrite ( bb , _, v , _) and
@@ -71,30 +69,22 @@ private predicate hasVariableWriteWithCapturedRead(BasicBlock bb, LocalVariable
71
69
}
72
70
73
71
/**
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`.
76
74
*/
77
- private predicate capturedCallRead ( BasicBlock bb , int i , LocalVariable v ) {
75
+ private predicate capturedCallRead ( Call call , BasicBlock bb , int i , LocalVariable v ) {
78
76
exists ( CfgScope scope |
79
77
hasVariableWriteWithCapturedRead ( bb .getAPredecessor * ( ) , v , scope ) and
80
- bb .getNode ( i ) .getNode ( ) instanceof Call
78
+ call = bb .getNode ( i ) .getNode ( )
81
79
|
82
- not scope instanceof Block
83
- or
84
80
// If the read happens inside a block, we restrict to the call that
85
81
// 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 ( )
87
85
)
88
86
}
89
87
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
-
98
88
/** Holds if `v` is read at index `i` in basic block `bb`. */
99
89
private predicate variableReadActual ( BasicBlock bb , int i , LocalVariable v ) {
100
90
exists ( VariableReadAccess read |
@@ -107,21 +97,38 @@ predicate variableRead(BasicBlock bb, int i, LocalVariable v, boolean certain) {
107
97
variableReadActual ( bb , i , v ) and
108
98
certain = true
109
99
or
110
- capturedCallRead ( bb , i , v ) and
100
+ capturedCallRead ( _ , bb , i , v ) and
111
101
certain = false
112
102
or
113
103
capturedExitRead ( bb , i , v ) and
114
104
certain = false
115
105
}
116
106
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
+
117
128
pragma [ noinline]
118
129
private predicate hasVariableReadWithCapturedWrite ( BasicBlock bb , LocalVariable v , CfgScope scope ) {
119
130
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 )
125
132
}
126
133
127
134
cached
@@ -137,20 +144,20 @@ private module Cached {
137
144
}
138
145
139
146
/**
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
141
148
* that writes captured variable `v`.
142
149
*/
143
150
cached
144
- predicate capturedCallWrite ( BasicBlock bb , int i , LocalVariable v ) {
151
+ predicate capturedCallWrite ( Call call , BasicBlock bb , int i , LocalVariable v ) {
145
152
exists ( CfgScope scope |
146
153
hasVariableReadWithCapturedWrite ( bb .getASuccessor * ( ) , v , scope ) and
147
- bb .getNode ( i ) .getNode ( ) instanceof Call
154
+ call = bb .getNode ( i ) .getNode ( )
148
155
|
149
- not scope instanceof Block
150
- or
151
156
// If the write happens inside a block, we restrict to the call that
152
157
// 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 ( )
154
161
)
155
162
}
156
163
@@ -180,6 +187,26 @@ private module Cached {
180
187
)
181
188
}
182
189
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
+
183
210
/**
184
211
* Holds if there is flow for a captured variable from the enclosing scope into a block.
185
212
* ```rb
@@ -191,13 +218,35 @@ private module Cached {
191
218
*/
192
219
cached
193
220
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 |
195
238
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 ( )
201
250
)
202
251
}
203
252
@@ -213,13 +262,15 @@ private module Cached {
213
262
*/
214
263
cached
215
264
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 ( )
223
274
)
224
275
}
225
276
0 commit comments