1
+ /**
2
+ * Provides classes and predicates for working with captured variables.
3
+ *
4
+ * We model reads from and writes to captured variable using field-flow.
5
+ *
6
+ * Example:
7
+ *
8
+ * ```rb
9
+ * def foo
10
+ * x = 1
11
+ * puts x
12
+ * fn = -> {
13
+ * puts x
14
+ * x = 2
15
+ * }
16
+ * fn.call
17
+ * puts x
18
+ * end
19
+ * ```
20
+ *
21
+ * We create a new kind of synthetic local variable, one per capturing callable,
22
+ * in this case `fn`, let's call it `fn_closure`.
23
+ *
24
+ * `fn_closure` will have an implicit entry definition at the start of `foo`,
25
+ * as well as at the start of the lambda itself, in which it behaves like a
26
+ * `self` parameter.
27
+ *
28
+ * Writes to captured variables will be turned into store steps, where the target
29
+ * of the store is the synthetic variable, and dually for reads.
30
+ *
31
+ * ```rb
32
+ * def foo
33
+ * # fn_closure = implicit
34
+ * x = 1 # fn_closure.x = 1
35
+ * puts x # puts fn_closure.x
36
+ * fn = -> {
37
+ * # fn_closure = self
38
+ * puts x # puts fn_closure.x
39
+ * x = 2 # fn_closure.x = 2
40
+ * }
41
+ * fn.call
42
+ * puts x # puts fn_closure.x
43
+ * end
44
+ * ```
45
+ *
46
+ * We will use SSA to construct adjacent use-use steps for `fn_closure`.
47
+ *
48
+ * Now we need to actually model flow in/out of the lambda. We do this by
49
+ * adding another synthetic access to `fn_closure` at the very definiton
50
+ * of the lambda, add a local flow step from this access to the lambda
51
+ * itself, and ensure that the receiver of a lambda call is a `self` argument:
52
+ *
53
+ * ```rb
54
+ * def foo
55
+ * # fn_closure = implicit
56
+ * x = 1 # fn_closure.x = 1
57
+ * puts x # puts fn_closure.x
58
+ * fn = # fn_closure, local step into the lambda below
59
+ * -> {
60
+ * # fn_closure = self
61
+ * puts x # puts fn_closure.x
62
+ * x = 2 # fn_closure.x = 2
63
+ * }
64
+ * fn.call # fn is a `self` argument
65
+ * puts x # puts fn_closure.x
66
+ * end
67
+ * ```
68
+ *
69
+ * The above ensures flow _into_ the lambda. For flow out
70
+ */
71
+
1
72
private import codeql.ssa.Ssa as SsaImplCommon
2
73
private import codeql.ruby.AST
3
74
private import codeql.ruby.CFG as Cfg
4
75
private import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared as ControlFlowGraphImplShared
5
76
private import codeql.ruby.dataflow.SSA
6
- private import codeql.ruby.ast.Variable
7
77
private import Cfg:: CfgNodes:: ExprNodes
78
+ private import DataFlowPrivate as DataFlowPrivate
79
+ private import codeql.util.Unit
8
80
9
- class CapturedVariableAccess extends LocalVariableAccess {
81
+ private class CapturedVariableAccess extends LocalVariableAccess {
10
82
CapturedVariableAccess ( ) { this .isCapturedAccess ( ) }
11
83
}
12
84
85
+ /** A variable that is captured. */
13
86
class CapturedVariable extends LocalVariable {
14
87
CapturedVariable ( ) { this .isCaptured ( ) }
15
88
}
16
89
90
+ /** A callable that captures a local variable. */
17
91
class CapturingCallable extends Callable {
18
92
CapturingCallable ( ) { any ( CapturedVariableAccess access ) .getCfgScope ( ) = this }
19
93
94
+ /** Gets a local variable captured by this callable. */
20
95
CapturedVariable getACapturedVariable ( ) { result .getAnAccess ( ) .getCfgScope ( ) = this }
21
96
}
22
97
23
- private predicate entryWrite ( Cfg:: EntryBasicBlock bb , int i , CapturingCallable v ) {
24
- i = - 1 and
25
- (
26
- bb .getScope ( ) = v
27
- or
28
- bb .getScope ( ) = v .getACapturedVariable ( ) .getDeclaringScope ( )
29
- )
30
- }
31
-
98
+ // private predicate entryWrite(Cfg::EntryBasicBlock bb, int i, CapturingCallable c) {
99
+ // i = -1 and
100
+ // (
101
+ // bb.getScope() = c
102
+ // or
103
+ // bb.getScope() = c.getACapturedVariable().getDeclaringScope()
104
+ // )
105
+ // }
106
+ // private predicate entryWrite(Cfg::EntryBasicBlock bb, CapturingCallable c) {
107
+ // bb.getScope() instanceof CapturingCallable
108
+ // or
109
+ // or
110
+ // bb.getScope() = c.getACapturedVariable().getDeclaringScope()
111
+ // )
112
+ // }
32
113
private predicate captureRead1 (
33
- Cfg:: BasicBlock bb , int i , CapturedVariable var , VariableAccessCfgNode access , CapturingCallable v
114
+ Cfg:: BasicBlock bb , int i , CapturedVariable var , VariableAccessCfgNode access , CapturingCallable c
34
115
) {
35
116
bb .getNode ( i ) = access and
36
117
access .getNode ( ) = var .getAnAccess ( ) and
37
- var = v .getACapturedVariable ( ) and
38
- ( bb .getScope ( ) = v or bb .getScope ( ) = var .getDefiningAccess ( ) .getCfgScope ( ) )
118
+ var = c .getACapturedVariable ( ) and
119
+ ( bb .getScope ( ) = c or bb .getScope ( ) = var .getDefiningAccess ( ) .getCfgScope ( ) )
39
120
}
40
121
41
- private predicate captureRead2 ( Cfg:: BasicBlock bb , int i , CapturingCallable v ) {
42
- bb .getNode ( i ) .getNode ( ) = v
122
+ private predicate captureRead2 ( Cfg:: BasicBlock bb , int i , Callable c ) {
123
+ bb .getNode ( i ) .getNode ( ) = c
43
124
}
44
125
45
- newtype TCaptureAccess =
46
- TEntryDef ( Cfg:: EntryBasicBlock bb , int i , CapturingCallable v ) { entryWrite ( bb , i , v ) } or
126
+ private predicate captureRead3 ( Cfg:: BasicBlock bb , int i ) {
127
+ // todo: BlockParameterNode
128
+ // DataFlowPrivate::lambdaCall(_, bb.getNode(i)) and
129
+ // (
130
+ // bb.getScope() = c
131
+ // or
132
+ // bb.getScope() = c.getACapturedVariable().getDeclaringScope()
133
+ // )
134
+ bb .getNode ( i ) instanceof DataFlowPrivate:: Argument
135
+ or
136
+ DataFlowPrivate:: isBlockArgument ( _, bb .getNode ( i ) )
137
+ }
138
+
139
+ cached
140
+ private newtype TCaptureAccess =
141
+ TEntryDef ( Cfg:: EntryBasicBlock bb ) or
47
142
TVariableAccess ( VariableAccessCfgNode access , CapturingCallable v ) {
48
143
captureRead1 ( _, _, _, access , v )
49
144
} or
50
- TLambdaAccess ( Cfg:: BasicBlock bb , int i , CapturingCallable v ) { captureRead2 ( bb , i , v ) }
145
+ TLambdaAccess ( Cfg:: BasicBlock bb , int i , Callable c ) { captureRead2 ( bb , i , c ) } or
146
+ TCallAccess ( Cfg:: BasicBlock bb , int i ) { captureRead3 ( bb , i ) }
51
147
52
148
class CaptureAccess extends TCaptureAccess {
53
149
string toString ( ) { result = "<captured variable>" }
54
150
55
151
Location getLocation ( ) {
56
- exists ( Cfg:: EntryBasicBlock bb , int i , CapturingCallable v |
57
- this = TEntryDef ( bb , i , v ) and
152
+ exists ( Cfg:: EntryBasicBlock bb |
153
+ this = TEntryDef ( bb ) and
58
154
result = bb .getLocation ( )
59
155
)
60
156
or
61
157
exists ( VariableAccessCfgNode access | this = TVariableAccess ( access , _) |
62
158
result = access .getLocation ( )
63
159
)
64
160
or
65
- exists ( CapturingCallable c |
161
+ exists ( Callable c |
66
162
this = TLambdaAccess ( _, _, c ) and
67
163
result = c .getLocation ( )
68
164
)
165
+ or
166
+ exists ( Cfg:: BasicBlock bb , int i |
167
+ this = TCallAccess ( bb , i ) and
168
+ result = bb .getNode ( i ) .getLocation ( )
169
+ )
69
170
}
70
171
71
172
Cfg:: CfgScope getCfgScope ( ) {
72
- exists ( Cfg:: EntryBasicBlock bb , int i , CapturingCallable v |
73
- this = TEntryDef ( bb , i , v ) and
173
+ exists ( Cfg:: EntryBasicBlock bb |
174
+ this = TEntryDef ( bb ) and
74
175
result = bb .getScope ( )
75
176
)
76
177
or
77
178
exists ( VariableAccessCfgNode access | this = TVariableAccess ( access , _) |
78
179
result = access .getScope ( )
79
180
)
80
181
or
81
- exists ( CapturingCallable c |
82
- this = TLambdaAccess ( _, _, c ) and
83
- result = c .getCfgScope ( )
182
+ exists ( Cfg:: BasicBlock bb |
183
+ this = TLambdaAccess ( bb , _, _) and
184
+ result = bb .getScope ( )
185
+ )
186
+ or
187
+ exists ( Cfg:: BasicBlock bb |
188
+ this = TCallAccess ( bb , _) and
189
+ result = bb .getScope ( )
84
190
)
85
191
}
86
192
87
- predicate isLambdaAccess ( Cfg:: BasicBlock bb , int i , CapturingCallable v ) {
88
- captureRead2 ( bb , i , v ) and
89
- this = TLambdaAccess ( bb , i , v )
90
- }
193
+ predicate isLambdaAccess ( Cfg:: BasicBlock bb , int i , Callable c ) { this = TLambdaAccess ( bb , i , c ) }
194
+
195
+ predicate isCallAccess ( Cfg:: BasicBlock bb , int i ) { this = TCallAccess ( bb , i ) }
91
196
92
- predicate isRead ( Cfg:: BasicBlock bb , int i , CapturingCallable v ) {
93
- exists ( VariableAccessCfgNode acc |
197
+ predicate isRead ( Cfg:: BasicBlock bb , int i ) {
198
+ exists ( VariableAccessCfgNode acc , CapturingCallable v |
94
199
captureRead1 ( bb , i , _, acc , v ) and
95
200
this = TVariableAccess ( acc , v )
96
201
)
97
202
or
98
- this .isLambdaAccess ( bb , i , v )
203
+ this .isLambdaAccess ( bb , i , _)
204
+ or
205
+ this .isCallAccess ( bb , i )
99
206
}
100
207
101
- predicate isWrite ( Cfg:: BasicBlock bb , int i , CapturingCallable v ) {
102
- entryWrite ( bb , i , v ) and
103
- this = TEntryDef ( bb , i , v )
104
- }
208
+ predicate isWrite ( Cfg:: BasicBlock bb , int i ) { this = TEntryDef ( bb ) and i = - 1 }
105
209
106
- predicate isReadOrWrite ( Cfg:: BasicBlock bb , int i , CapturingCallable v ) {
107
- this .isRead ( bb , i , v ) or
108
- this .isWrite ( bb , i , v )
210
+ predicate isReadOrWrite ( Cfg:: BasicBlock bb , int i ) {
211
+ this .isRead ( bb , i ) or
212
+ this .isWrite ( bb , i )
109
213
}
110
214
}
111
215
@@ -120,20 +224,22 @@ private module SsaInput implements SsaImplCommon::InputSig {
120
224
121
225
class ExitBasicBlock = BasicBlocks:: ExitBasicBlock ;
122
226
123
- class SourceVariable = CapturingCallable ;
227
+ class SourceVariable = Unit ;
124
228
125
229
/**
126
230
* Holds if the statement at index `i` of basic block `bb` contains a write to variable `v`.
127
231
* `certain` is true if the write definitely occurs.
128
232
*/
129
233
predicate variableWrite ( BasicBlock bb , int i , SourceVariable v , boolean certain ) {
130
- any ( CaptureAccess access ) .isWrite ( bb , i , v ) and
131
- certain = true
234
+ any ( CaptureAccess access ) .isWrite ( bb , i ) and
235
+ certain = true and
236
+ exists ( v )
132
237
}
133
238
134
239
predicate variableRead ( BasicBlock bb , int i , SourceVariable v , boolean certain ) {
135
- any ( CaptureAccess access ) .isRead ( bb , i , v ) and
136
- certain = true
240
+ any ( CaptureAccess access ) .isRead ( bb , i ) and
241
+ certain = true and
242
+ exists ( v )
137
243
}
138
244
}
139
245
@@ -142,13 +248,9 @@ private import SsaImplCommon::Make<SsaInput> as Impl
142
248
class Definition = Impl:: Definition ;
143
249
144
250
predicate adjacentStep ( CaptureAccess access1 , CaptureAccess access2 ) {
145
- exists (
146
- Definition def , SsaInput:: BasicBlock bb1 , int i1 , SsaInput:: BasicBlock bb2 , int i2 ,
147
- SsaInput:: SourceVariable v
148
- |
251
+ exists ( Definition def , SsaInput:: BasicBlock bb1 , int i1 , SsaInput:: BasicBlock bb2 , int i2 |
149
252
Impl:: adjacentDefRead ( def , bb1 , i1 , bb2 , i2 ) and
150
- v = def .getSourceVariable ( ) and
151
- access1 .isReadOrWrite ( bb1 , i1 , v ) and
152
- access2 .isReadOrWrite ( bb2 , i2 , v )
253
+ access1 .isReadOrWrite ( bb1 , i1 ) and
254
+ access2 .isReadOrWrite ( bb2 , i2 )
153
255
)
154
256
}
0 commit comments