-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathConstant.qll
More file actions
367 lines (313 loc) · 10.7 KB
/
Constant.qll
File metadata and controls
367 lines (313 loc) · 10.7 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
overlay[local]
module;
private import codeql.ruby.AST
private import internal.AST
private import internal.Constant
private import internal.Module
private import internal.Variable
private import internal.TreeSitter
/** A constant value. */
overlay[global]
class ConstantValue extends TConstantValue {
/** Gets a textual representation of this constant value. */
final string toString() { this.hasValueWithType(result, _) }
/** Gets a string describing the type of this constant value. */
string getValueType() { this.hasValueWithType(_, result) }
private predicate hasValueWithType(string value, string type) {
value = this.getInt().toString() and type = "int"
or
value = this.getFloat().toString() and type = "float"
or
exists(int numerator, int denominator |
this.isRational(numerator, denominator) and
value = numerator + "/" + denominator and
type = "rational"
)
or
exists(float real, float imaginary |
this.isComplex(real, imaginary) and
value = real + "+" + imaginary + "i" and
type = "complex"
)
or
value = this.getString() and type = "string"
or
value = ":" + this.getSymbol() and type = "symbol"
or
value = this.getRegExp() and type = "regexp"
or
value = this.getBoolean().toString() and type = "boolean"
or
this.isNil() and value = "nil" and type = "nil"
}
/** Gets the integer value, if this is an integer. */
int getInt() { this = TInt(result) }
/** Holds if this is the integer value `i`. */
predicate isInt(int i) { i = this.getInt() }
/** Gets the float value, if this is a float. */
float getFloat() { this = TFloat(result) }
/** Holds if this is the float value `f`. */
predicate isFloat(float f) { f = this.getFloat() }
/** Holds if this is the rational value `numerator / denominator`. */
predicate isRational(int numerator, int denominator) { this = TRational(numerator, denominator) }
/** Holds if this is the complex value `real + imaginary * i`. */
predicate isComplex(float real, float imaginary) { this = TComplex(real, imaginary) }
/** Gets the string value, if this is a string. */
string getString() { this = TString(result) }
/** Holds if this is the string value `s`. */
predicate isString(string s) { s = this.getString() }
/** Gets the symbol value (excluding the `:` prefix), if this is a symbol. */
string getSymbol() { this = TSymbol(result) }
/** Holds if this is the symbol value `:s`. */
predicate isSymbol(string s) { s = this.getSymbol() }
/** Gets the regexp value, if this is a regexp. */
string getRegExp() { this.isRegExpWithFlags(result, _) }
/** Holds if this is the regexp value `/s/`, ignoring any flags. */
predicate isRegExp(string s) { this.isRegExpWithFlags(s, _) }
/** Holds if this is the regexp value `/s/flags` . */
predicate isRegExpWithFlags(string s, string flags) { this = TRegExp(s, flags) }
/** Gets the string/symbol/regexp value, if any. */
string getStringlikeValue() { result = [this.getString(), this.getSymbol(), this.getRegExp()] }
/**
* Holds if this is:
* - the string value `s`,
* - the symbol value `:s`, or
* - the regexp value `/s/`.
*/
predicate isStringlikeValue(string s) { s = this.getStringlikeValue() }
/** Gets the Boolean value, if this is a Boolean. */
boolean getBoolean() { this = TBoolean(result) }
/** Holds if this is the Boolean value `b`. */
predicate isBoolean(boolean b) { b = this.getBoolean() }
/** Holds if this is the `nil` value. */
predicate isNil() { this = TNil() }
/** Gets a (unique) serialized version of this value. */
final string serialize() {
result = this.getInt().toString()
or
exists(string res | res = this.getFloat().toString() |
if exists(res.indexOf(".")) then result = res else result = res + ".0"
)
or
exists(int numerator, int denominator |
this.isRational(numerator, denominator) and
result = numerator + "/" + denominator
)
or
exists(float real, float imaginary |
this.isComplex(real, imaginary) and
result = real + "+" + imaginary + "i"
)
or
result = "\"" + this.getString().replaceAll("\"", "\\\"") + "\""
or
result = ":" + this.getSymbol()
or
exists(string s, string flags |
this.isRegExpWithFlags(s, flags) and
result = "/" + s + "/" + flags
)
or
result = this.getBoolean().toString()
or
this.isNil() and result = "nil"
}
}
/** Provides different sub classes of `ConstantValue`. */
overlay[global]
module ConstantValue {
/** A constant integer value. */
class ConstantIntegerValue extends ConstantValue, TInt { }
/** A constant float value. */
class ConstantFloatValue extends ConstantValue, TFloat { }
/** A constant rational value. */
class ConstantRationalValue extends ConstantValue, TRational { }
/** A constant complex value. */
class ConstantComplexValue extends ConstantValue, TComplex { }
/** A constant string-like value. */
class ConstantStringlikeValue extends ConstantValue, TStringlike { }
/** A constant string value. */
class ConstantStringValue extends ConstantStringlikeValue, TString { }
/** A constant symbol value. */
class ConstantSymbolValue extends ConstantStringlikeValue, TSymbol { }
/** A constant regexp value. */
class ConstantRegExpValue extends ConstantStringlikeValue, TRegExp { }
/** A constant Boolean value. */
class ConstantBooleanValue extends ConstantValue, TBoolean { }
/** A constant `nil` value. */
class ConstantNilValue extends ConstantValue, TNil { }
/** Gets the integer constant `x`. */
ConstantValue fromInt(int x) { result.getInt() = x }
/** Gets the float constant `x`. */
ConstantValue fromFloat(float x) { result.getFloat() = x }
/** Gets the string constant `x`. */
ConstantValue fromString(string x) { result.getString() = x }
/** Gets the symbol constant `x`. */
ConstantValue fromSymbol(string x) { result.getSymbol() = x }
/** Gets the regexp constant `x`. */
ConstantValue fromRegExp(string x) { result.getRegExp() = x }
/** Gets the string, symbol, or regexp constant `x`. */
ConstantValue fromStringlikeValue(string x) { result.getStringlikeValue() = x }
}
/** An access to a constant. */
class ConstantAccess extends Expr, TConstantAccess {
/** Gets the name of the constant being accessed. */
string getName() { none() }
/** Holds if the name of the constant being accessed is `name`. */
final predicate hasName(string name) { this.getName() = name }
/**
* Gets the expression used in the access's scope resolution operation, if
* any. In the following example, the result is the `Call` expression for
* `foo()`.
*
* ```rb
* foo()::MESSAGE
* ```
*
* However, there is no result for the following example, since there is no
* scope resolution operation.
*
* ```rb
* MESSAGE
* ```
*/
Expr getScopeExpr() { none() }
/**
* Holds if the access uses the scope resolution operator to refer to the
* global scope, as in this example:
*
* ```rb
* ::MESSAGE
* ```
*/
predicate hasGlobalScope() { none() }
override string toString() { result = this.getName() }
override AstNode getAChild(string pred) {
result = super.getAChild(pred)
or
pred = "getScopeExpr" and result = this.getScopeExpr()
}
}
/**
* A use (read) of a constant.
*
* For example, the right-hand side of the assignment in:
*
* ```rb
* x = Foo
* ```
*
* Or the superclass `Bar` in this example:
*
* ```rb
* class Foo < Bar
* end
* ```
*/
class ConstantReadAccess extends ConstantAccess {
ConstantReadAccess() {
not this instanceof ConstantWriteAccess
or
// `X` in `X ||= 10` is considered both a read and a write
this = any(AssignOperation a).getLeftOperand()
or
this instanceof TConstantReadAccessSynth
}
/**
* Gets the value being read, if any. For example, in
*
* ```rb
* module M
* CONST = "const"
* end
*
* puts M::CONST
* ```
*
* the value being read at `M::CONST` is `"const"`.
*/
overlay[global]
Expr getValue() { result = getConstantReadAccessValue(this) }
/**
* Gets a fully qualified name for this constant read, based on the context in
* which it occurs.
*/
overlay[global]
string getAQualifiedName() { result = resolveConstant(this) }
/** Gets the module that this read access resolves to, if any. */
overlay[global]
Module getModule() { result = resolveConstantReadAccess(this) }
final override string getAPrimaryQlClass() { result = "ConstantReadAccess" }
}
/**
* A definition of a constant.
*
* Examples:
*
* ```rb
* Foo = 1 # defines constant Foo as an integer
* M::Foo = 1 # defines constant Foo as an integer in module M
*
* class Bar; end # defines constant Bar as a class
* class M::Bar; end # defines constant Bar as a class in module M
*
* module Baz; end # defines constant Baz as a module
* module M::Baz; end # defines constant Baz as a module in module M
* ```
*/
class ConstantWriteAccess extends ConstantAccess {
ConstantWriteAccess() {
explicitAssignmentNode(toGenerated(this), _) or
this instanceof TNamespace or
this instanceof TConstantWriteAccessSynth
}
override string getAPrimaryQlClass() { result = "ConstantWriteAccess" }
/**
* Gets a fully qualified name for this constant, based on the context in
* which it is defined.
*
* For example, given
* ```rb
* module Foo
* module Bar
* class Baz
* end
* end
* CONST_A = "a"
* end
* ```
*
* the constant `Baz` has the fully qualified name `Foo::Bar::Baz`, and
* `CONST_A` has the fully qualified name `Foo::CONST_A`.
*
* Important note: This can return more than one value, because there are
* situations where there can be multiple possible "fully qualified" names.
* For example:
* ```
* module Mod4
* include Mod1
* module Mod3::Mod5 end
* end
* ```
* In the above snippet, `Mod5` has two valid fully qualified names it can be
* referred to by: `Mod1::Mod3::Mod5`, or `Mod4::Mod3::Mod5`.
*
* Another example has to do with the order in which module definitions are
* executed at runtime. Because of the way that ruby dynamically looks up
* constants up the namespace chain, the fully qualified name of a nested
* constant can be ambiguous from just statically looking at the AST.
*/
overlay[global]
string getAQualifiedName() { result = resolveConstantWrite(this) }
}
/**
* A definition of a constant via assignment. For example, the left-hand
* operand in the following example:
*
* ```rb
* MAX_SIZE = 100
* ```
*/
class ConstantAssignment extends ConstantWriteAccess, LhsExpr {
override string getAPrimaryQlClass() { result = "ConstantAssignment" }
}