@@ -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+ */
5448pragma [ noinline]
5549private 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+ */
6159pragma [ noinline]
6260private 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`. */
9989private 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+
117128pragma [ noinline]
118129private 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
127134cached
@@ -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
0 commit comments