-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathMethod.qll
More file actions
402 lines (345 loc) · 10.2 KB
/
Method.qll
File metadata and controls
402 lines (345 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
overlay[local]
module;
private import codeql.ruby.AST
private import codeql.ruby.controlflow.ControlFlowGraph
private import internal.AST
private import internal.TreeSitter
private import internal.Method
/** A callable. */
class Callable extends StmtSequence, Expr, Scope, TCallable {
/** Gets the number of parameters of this callable. */
final int getNumberOfParameters() { result = count(this.getAParameter()) }
/** Gets a parameter of this callable. */
final Parameter getAParameter() { result = this.getParameter(_) }
/** Gets the `n`th parameter of this callable. */
Parameter getParameter(int n) { none() }
override AstNode getAChild(string pred) {
result = super.getAChild(pred)
or
pred = "getParameter" and result = this.getParameter(_)
}
}
/** A method. */
class MethodBase extends Callable, BodyStmt, Scope, TMethodBase {
/** Gets the name of this method. */
string getName() { none() }
/** Holds if the name of this method is `name`. */
final predicate hasName(string name) { this.getName() = name }
override AstNode getAChild(string pred) {
result = Callable.super.getAChild(pred)
or
result = BodyStmt.super.getAChild(pred)
}
/**
* Holds if this method is public.
* Methods are public by default.
*/
overlay[global]
predicate isPublic() { this.getVisibility() = "public" }
/** Holds if this method is private. */
overlay[global]
predicate isPrivate() { this.getVisibility() = "private" }
/** Holds if this method is protected. */
overlay[global]
predicate isProtected() { this.getVisibility() = "protected" }
/**
* Gets a string describing the visibility of this method.
* This is either 'public', 'private' or 'protected'.
*/
overlay[global]
string getVisibility() {
result = getVisibilityModifier(this).getVisibility()
or
not exists(getVisibilityModifier(this)) and result = "public"
}
}
/**
* Gets the visibility modifier that explicitly sets the visibility of method
* `m`.
*
* Examples:
* ```rb
* def f
* end
* private :f
*
* private def g
* end
* ```
*/
overlay[global]
private VisibilityModifier getExplicitVisibilityModifier(Method m) {
result.getMethodArgument() = m
or
exists(ModuleBase n, string name |
methodIsDeclaredIn(m, n, name) and
modifiesIn(result, n, name)
)
}
/**
* Gets the visibility modifier that defines the visibility of method `m`, if
* any.
*/
overlay[global]
private VisibilityModifier getVisibilityModifier(MethodBase mb) {
mb =
any(Method m |
result = getExplicitVisibilityModifier(m)
or
not exists(getExplicitVisibilityModifier(m)) and
exists(ModuleBase n, int methodPos | isDeclaredIn(m, n, methodPos) |
// The relevant visibility modifier is the closest call that occurs before
// the definition of `m` (typically this means higher up the file).
result =
max(int modifierPos, VisibilityModifier modifier |
modifier.modifiesAmbientVisibility() and
isDeclaredIn(modifier, n, modifierPos) and
modifierPos < methodPos
|
modifier order by modifierPos
)
)
)
or
mb =
any(SingletonMethod m |
result.getMethodArgument() = m
or
exists(ModuleBase n, string name |
methodIsDeclaredIn(m, n, name) and
modifiesIn(result, n, name)
)
)
}
/**
* A method call that sets the visibility of other methods.
* For example, `private :foo` makes the method `foo` private.
*/
private class VisibilityModifier extends MethodCall {
VisibilityModifier() {
this.getMethodName() =
["public", "private", "protected", "public_class_method", "private_class_method"]
}
/** Gets the name of the method that this call applies to. */
Expr getMethodArgument() { result = this.getArgument(0) }
/**
* Holds if this modifier changes the "ambient" visibility - i.e. the default
* visibility of any subsequent method definitions.
*/
predicate modifiesAmbientVisibility() {
this.getMethodName() = ["public", "private", "protected"] and
this.getNumberOfArguments() = 0
}
/** Gets the visibility set by this modifier. */
string getVisibility() {
this.getMethodName() = ["public", "public_class_method"] and result = "public"
or
this.getMethodName() = ["private", "private_class_method"] and
result = "private"
or
this.getMethodName() = "protected" and
result = "protected"
}
}
/** A normal method. */
class Method extends MethodBase, TMethod {
private Ruby::Method g;
Method() { this = TMethod(g) }
final override string getAPrimaryQlClass() { result = "Method" }
final override string getName() {
result = g.getName().(Ruby::Token).getValue() or
result = g.getName().(Ruby::Setter).getName().getValue() + "="
}
/**
* Holds if this is a setter method, as in the following example:
* ```rb
* class Person
* def name=(n)
* @name = n
* end
* end
* ```
*/
final predicate isSetter() { g.getName() instanceof Ruby::Setter }
/**
* Holds if this method is private. All methods with the name prefix
* `private` are private below:
*
* ```rb
* class C
* private def private1
* end
*
* def public
* end
*
* def private2
* end
* private :private2
*
* private
*
* def private3
* end
*
* def private4
* end
* end
* ```
*/
overlay[global]
override predicate isPrivate() { super.isPrivate() }
final override Parameter getParameter(int n) {
toGenerated(result) = g.getParameters().getChild(n)
}
final override string toString() { result = this.getName() }
overlay[global]
override string getVisibility() {
result = getVisibilityModifier(this).getVisibility()
or
this.getEnclosingModule() instanceof Toplevel and
not exists(getVisibilityModifier(this)) and
result = "private"
or
not this.getEnclosingModule() instanceof Toplevel and
not exists(getVisibilityModifier(this)) and
result = "public"
}
}
overlay[global]
pragma[nomagic]
private predicate modifiesIn(VisibilityModifier vm, ModuleBase n, string name) {
n = vm.getEnclosingModule() and
name = vm.getMethodArgument().getConstantValue().getStringlikeValue()
}
/**
* Holds if statement `s` is declared in namespace `n` at position `pos`.
*/
pragma[nomagic]
private predicate isDeclaredIn(Stmt s, ModuleBase n, int pos) {
n = s.getEnclosingModule() and
n.getStmt(pos) = s
}
/**
* Holds if method `m` with name `name` is declared in namespace `n`.
*/
pragma[nomagic]
private predicate methodIsDeclaredIn(MethodBase m, ModuleBase n, string name) {
isDeclaredIn(m, n, _) and
name = m.getName()
}
/** A singleton method. */
class SingletonMethod extends MethodBase, TSingletonMethod {
private Ruby::SingletonMethod g;
SingletonMethod() { this = TSingletonMethod(g) }
final override string getAPrimaryQlClass() { result = "SingletonMethod" }
/** Gets the object of this singleton method. */
final Expr getObject() { toGenerated(result) = g.getObject() }
final override string getName() {
result = g.getName().(Ruby::Token).getValue()
or
result = g.getName().(Ruby::Setter).getName().getValue() + "="
}
final override Parameter getParameter(int n) {
toGenerated(result) = g.getParameters().getChild(n)
}
final override string toString() { result = this.getName() }
final override AstNode getAChild(string pred) {
result = super.getAChild(pred)
or
pred = "getObject" and result = this.getObject()
}
/**
* Holds if this method is private. All methods with the name prefix
* `private` are private below:
*
* ```rb
* class C
* private_class_method def self.private1
* end
*
* def self.public
* end
*
* def self.private2
* end
* private_class_method :private2
*
* private # this has no effect on singleton methods
*
* def self.public2
* end
* end
* ```
*/
overlay[global]
override predicate isPrivate() { super.isPrivate() }
}
/**
* A lambda (anonymous method). For example:
* ```rb
* -> (x) { x + 1 }
* ```
*/
class Lambda extends Callable, BodyStmt, TLambda {
private Ruby::Lambda g;
Lambda() { this = TLambda(g) }
final override string getAPrimaryQlClass() { result = "Lambda" }
final override Parameter getParameter(int n) {
toGenerated(result) = g.getParameters().getChild(n)
}
final override string toString() { result = "-> { ... }" }
final override AstNode getAChild(string pred) {
result = Callable.super.getAChild(pred)
or
result = BodyStmt.super.getAChild(pred)
}
}
/** A block. */
class Block extends Callable, StmtSequence, Scope, TBlock {
/**
* Gets a local variable declared by this block.
* For example `local` in `{ | param; local| puts param }`.
*/
LocalVariableWriteAccess getALocalVariable() { result = this.getLocalVariable(_) }
/**
* Gets the `n`th local variable declared by this block.
* For example `local` in `{ | param; local| puts param }`.
*/
LocalVariableWriteAccess getLocalVariable(int n) { none() }
override AstNode getAChild(string pred) {
result = Callable.super.getAChild(pred)
or
result = StmtSequence.super.getAChild(pred)
or
pred = "getLocalVariable" and result = this.getLocalVariable(_)
}
}
/** A block enclosed within `do` and `end`. */
class DoBlock extends Block, BodyStmt, TDoBlock {
private Ruby::DoBlock g;
DoBlock() { this = TDoBlock(g) }
final override LocalVariableWriteAccess getLocalVariable(int n) {
toGenerated(result) = g.getParameters().getLocals(n)
}
final override Parameter getParameter(int n) {
toGenerated(result) = g.getParameters().getChild(n)
}
final override string toString() { result = "do ... end" }
final override AstNode getAChild(string pred) {
result = Block.super.getAChild(pred)
or
result = BodyStmt.super.getAChild(pred)
}
final override string getAPrimaryQlClass() { result = "DoBlock" }
}
/**
* A block defined using curly braces, e.g. in the following code:
* ```rb
* names.each { |name| puts name }
* ```
*/
class BraceBlock extends Block, TBraceBlock {
final override string toString() { result = "{ ... }" }
final override string getAPrimaryQlClass() { result = "BraceBlock" }
}