Skip to content

Commit 1e39259

Browse files
authored
Merge pull request github#7750 from hvitved/ruby/desugar-hash-literals
Ruby: Desugar hash literals
2 parents ece952a + 280023c commit 1e39259

File tree

19 files changed

+627
-439
lines changed

19 files changed

+627
-439
lines changed

ruby/ql/lib/codeql/ruby/ast/Literal.qll

Lines changed: 10 additions & 340 deletions
Large diffs are not rendered by default.
Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
private import codeql.ruby.AST
2+
private import AST
3+
private import Constant
4+
private import TreeSitter
5+
private import codeql.ruby.controlflow.CfgNodes
6+
7+
int parseInteger(Ruby::Integer i) {
8+
exists(string s | s = i.getValue().toLowerCase().replaceAll("_", "") |
9+
s.charAt(0) != "0" and
10+
result = s.toInt()
11+
or
12+
exists(string str, string values, int shift |
13+
s.matches("0b%") and
14+
values = "01" and
15+
str = s.suffix(2) and
16+
shift = 1
17+
or
18+
s.matches("0x%") and
19+
values = "0123456789abcdef" and
20+
str = s.suffix(2) and
21+
shift = 4
22+
or
23+
s.charAt(0) = "0" and
24+
not s.charAt(1) = ["b", "x", "o"] and
25+
values = "01234567" and
26+
str = s.suffix(1) and
27+
shift = 3
28+
or
29+
s.matches("0o%") and
30+
values = "01234567" and
31+
str = s.suffix(2) and
32+
shift = 3
33+
|
34+
result =
35+
sum(int index, string c, int v, int exp |
36+
c = str.charAt(index) and
37+
v = values.indexOf(c.toLowerCase()) and
38+
exp = str.length() - index - 1
39+
|
40+
v.bitShiftLeft((str.length() - index - 1) * shift)
41+
)
42+
)
43+
)
44+
}
45+
46+
private class RequiredIntegerLiteralConstantValue extends RequiredConstantValue {
47+
override predicate requiredInt(int i) { i = any(IntegerLiteral il).getValue() }
48+
}
49+
50+
abstract class IntegerLiteralImpl extends Expr, TIntegerLiteral {
51+
abstract int getValueImpl();
52+
}
53+
54+
class IntegerLiteralReal extends IntegerLiteralImpl, TIntegerLiteralReal {
55+
private Ruby::Integer g;
56+
57+
IntegerLiteralReal() { this = TIntegerLiteralReal(g) }
58+
59+
final override int getValueImpl() { result = parseInteger(g) }
60+
61+
final override string toString() { result = g.getValue() }
62+
}
63+
64+
class IntegerLiteralSynth extends IntegerLiteralImpl, TIntegerLiteralSynth {
65+
private int value;
66+
67+
IntegerLiteralSynth() { this = TIntegerLiteralSynth(_, _, value) }
68+
69+
final override int getValueImpl() { result = value }
70+
71+
final override string toString() { result = value.toString() }
72+
}
73+
74+
// TODO: implement properly
75+
float parseFloat(Ruby::Float f) { result = f.getValue().toFloat() }
76+
77+
private class RequiredFloatConstantValue extends RequiredConstantValue {
78+
override predicate requiredFloat(float f) { f = parseFloat(_) }
79+
}
80+
81+
predicate isRationalValue(Ruby::Rational r, int numerator, int denominator) {
82+
numerator = parseInteger(r.getChild()) and
83+
denominator = 1
84+
or
85+
exists(Ruby::Float f, string regex, string before, string after |
86+
f = r.getChild() and
87+
regex = "([^.]*)\\.(.*)" and
88+
before = f.getValue().regexpCapture(regex, 1) and
89+
after = f.getValue().regexpCapture(regex, 2) and
90+
numerator = before.toInt() * denominator + after.toInt() and
91+
denominator = 10.pow(after.length())
92+
)
93+
}
94+
95+
private class RequiredRationalConstantValue extends RequiredConstantValue {
96+
override predicate requiredRational(int numerator, int denominator) {
97+
isRationalValue(_, numerator, denominator)
98+
}
99+
}
100+
101+
float getComplexValue(Ruby::Complex c) {
102+
exists(string s |
103+
s = c.getValue() and
104+
result = s.prefix(s.length() - 1).toFloat()
105+
)
106+
}
107+
108+
private class RequiredComplexConstantValue extends RequiredConstantValue {
109+
override predicate requiredComplex(float real, float imaginary) {
110+
real = 0 and
111+
imaginary = getComplexValue(_)
112+
}
113+
}
114+
115+
class TrueLiteral extends BooleanLiteral, TTrueLiteral {
116+
private Ruby::True g;
117+
118+
TrueLiteral() { this = TTrueLiteral(g) }
119+
120+
final override string toString() { result = g.getValue() }
121+
122+
final override predicate isTrue() { any() }
123+
}
124+
125+
class FalseLiteral extends BooleanLiteral, TFalseLiteral {
126+
private Ruby::False g;
127+
128+
FalseLiteral() { this = TFalseLiteral(g) }
129+
130+
final override string toString() { result = g.getValue() }
131+
132+
final override predicate isFalse() { any() }
133+
}
134+
135+
private class RequiredEncodingLiteralConstantValue extends RequiredConstantValue {
136+
override predicate requiredString(string s) { s = "UTF-8" }
137+
}
138+
139+
private class RequiredLineLiteralConstantValue extends RequiredConstantValue {
140+
override predicate requiredInt(int i) { i = any(LineLiteral ll).getLocation().getStartLine() }
141+
}
142+
143+
private class RequiredFileLiteralConstantValue extends RequiredConstantValue {
144+
override predicate requiredString(string s) {
145+
s = any(FileLiteral fl).getLocation().getFile().getAbsolutePath()
146+
}
147+
}
148+
149+
private class RequiredStringTextComponentConstantValue extends RequiredConstantValue {
150+
override predicate requiredString(string s) {
151+
s = any(Ruby::Token t | exists(TStringTextComponentNonRegexp(t))).getValue()
152+
}
153+
}
154+
155+
private class RequiredStringEscapeSequenceComponentConstantValue extends RequiredConstantValue {
156+
override predicate requiredString(string s) {
157+
s = any(Ruby::Token t | exists(TStringEscapeSequenceComponentNonRegexp(t))).getValue()
158+
}
159+
}
160+
161+
class TRegExpComponent =
162+
TStringTextComponentRegexp or TStringEscapeSequenceComponentRegexp or
163+
TStringInterpolationComponentRegexp;
164+
165+
// Exclude components that are children of a free-spacing regex.
166+
// We do this because `ParseRegExp.qll` cannot handle free-spacing regexes.
167+
string getRegExpTextComponentValue(RegExpTextComponent c) {
168+
exists(Ruby::Token t |
169+
c = TStringTextComponentRegexp(t) and
170+
not c.getParent().(RegExpLiteral).hasFreeSpacingFlag() and
171+
result = t.getValue()
172+
)
173+
}
174+
175+
private class RequiredRegExpTextComponentConstantValue extends RequiredConstantValue {
176+
override predicate requiredString(string s) { s = getRegExpTextComponentValue(_) }
177+
}
178+
179+
// Exclude components that are children of a free-spacing regex.
180+
// We do this because `ParseRegExp.qll` cannot handle free-spacing regexes.
181+
string getRegExpEscapeSequenceComponentValue(RegExpEscapeSequenceComponent c) {
182+
exists(Ruby::EscapeSequence e |
183+
c = TStringEscapeSequenceComponentRegexp(e) and
184+
not c.getParent().(RegExpLiteral).hasFreeSpacingFlag() and
185+
result = e.getValue()
186+
)
187+
}
188+
189+
private class RequiredRegExpEscapeSequenceComponentConstantValue extends RequiredConstantValue {
190+
override predicate requiredString(string s) { s = getRegExpEscapeSequenceComponentValue(_) }
191+
}
192+
193+
class RegularStringLiteral extends StringLiteral, TRegularStringLiteral {
194+
private Ruby::String g;
195+
196+
RegularStringLiteral() { this = TRegularStringLiteral(g) }
197+
198+
final override StringComponent getComponent(int n) { toGenerated(result) = g.getChild(n) }
199+
}
200+
201+
class BareStringLiteral extends StringLiteral, TBareStringLiteral {
202+
private Ruby::BareString g;
203+
204+
BareStringLiteral() { this = TBareStringLiteral(g) }
205+
206+
final override StringComponent getComponent(int n) { toGenerated(result) = g.getChild(n) }
207+
}
208+
209+
// Tree-sitter gives us value text including the colon, which we skip.
210+
string getSimpleSymbolValue(Ruby::SimpleSymbol ss) { result = ss.getValue().suffix(1) }
211+
212+
private class RequiredSimpleSymbolConstantValue extends RequiredConstantValue {
213+
override predicate requiredSymbol(string s) { s = getSimpleSymbolValue(_) }
214+
}
215+
216+
private class SimpleSymbolLiteral extends SymbolLiteral, TSimpleSymbolLiteral {
217+
private Ruby::SimpleSymbol g;
218+
219+
SimpleSymbolLiteral() { this = TSimpleSymbolLiteral(g) }
220+
221+
final override ConstantValue::ConstantSymbolValue getConstantValue() {
222+
result.isSymbol(getSimpleSymbolValue(g))
223+
}
224+
225+
final override string toString() { result = g.getValue() }
226+
}
227+
228+
class ComplexSymbolLiteral extends SymbolLiteral, TComplexSymbolLiteral { }
229+
230+
class DelimitedSymbolLiteral extends ComplexSymbolLiteral, TDelimitedSymbolLiteral {
231+
private Ruby::DelimitedSymbol g;
232+
233+
DelimitedSymbolLiteral() { this = TDelimitedSymbolLiteral(g) }
234+
235+
final override StringComponent getComponent(int i) { toGenerated(result) = g.getChild(i) }
236+
}
237+
238+
class BareSymbolLiteral extends ComplexSymbolLiteral, TBareSymbolLiteral {
239+
private Ruby::BareSymbol g;
240+
241+
BareSymbolLiteral() { this = TBareSymbolLiteral(g) }
242+
243+
final override StringComponent getComponent(int i) { toGenerated(result) = g.getChild(i) }
244+
}
245+
246+
private class RequiredHashKeySymbolConstantValue extends RequiredConstantValue {
247+
override predicate requiredSymbol(string s) { s = any(Ruby::HashKeySymbol h).getValue() }
248+
}
249+
250+
private class HashKeySymbolLiteral extends SymbolLiteral, THashKeySymbolLiteral {
251+
private Ruby::HashKeySymbol g;
252+
253+
HashKeySymbolLiteral() { this = THashKeySymbolLiteral(g) }
254+
255+
final override ConstantValue::ConstantSymbolValue getConstantValue() {
256+
result.isSymbol(g.getValue())
257+
}
258+
259+
final override string toString() { result = ":" + g.getValue() }
260+
}
261+
262+
private class RequiredCharacterConstantValue extends RequiredConstantValue {
263+
override predicate requiredString(string s) { s = any(Ruby::Character c).getValue() }
264+
}
265+
266+
abstract class ArrayLiteralImpl extends Literal, TArrayLiteral {
267+
abstract Expr getElementImpl(int n);
268+
269+
abstract int getNumberOfElementsImpl();
270+
}
271+
272+
class RegularArrayLiteral extends ArrayLiteralImpl, TRegularArrayLiteral {
273+
private Ruby::Array g;
274+
275+
RegularArrayLiteral() { this = TRegularArrayLiteral(g) }
276+
277+
final override Expr getElementImpl(int i) { toGenerated(result) = g.getChild(i) }
278+
279+
final override int getNumberOfElementsImpl() { result = count(g.getChild(_)) }
280+
281+
final override string toString() { result = "[...]" }
282+
}
283+
284+
class StringArrayLiteral extends ArrayLiteralImpl, TStringArrayLiteral {
285+
private Ruby::StringArray g;
286+
287+
StringArrayLiteral() { this = TStringArrayLiteral(g) }
288+
289+
final override Expr getElementImpl(int i) { toGenerated(result) = g.getChild(i) }
290+
291+
final override int getNumberOfElementsImpl() { result = count(g.getChild(_)) }
292+
293+
final override string toString() { result = "%w(...)" }
294+
}
295+
296+
class SymbolArrayLiteral extends ArrayLiteralImpl, TSymbolArrayLiteral {
297+
private Ruby::SymbolArray g;
298+
299+
SymbolArrayLiteral() { this = TSymbolArrayLiteral(g) }
300+
301+
final override Expr getElementImpl(int i) { toGenerated(result) = g.getChild(i) }
302+
303+
final override int getNumberOfElementsImpl() { result = count(g.getChild(_)) }
304+
305+
final override string toString() { result = "%i(...)" }
306+
}
307+
308+
class HashLiteralImpl extends Literal, THashLiteral {
309+
Ruby::Hash g;
310+
311+
HashLiteralImpl() { this = THashLiteral(g) }
312+
313+
final int getNumberOfElementsImpl() { result = count(g.getChild(_)) }
314+
}
315+
316+
class RangeLiteralReal extends RangeLiteral, TRangeLiteralReal {
317+
private Ruby::Range g;
318+
319+
RangeLiteralReal() { this = TRangeLiteralReal(g) }
320+
321+
final override Expr getBegin() { toGenerated(result) = g.getBegin() }
322+
323+
final override Expr getEnd() { toGenerated(result) = g.getEnd() }
324+
325+
final override predicate isInclusive() { g instanceof @ruby_range_dotdot }
326+
327+
final override predicate isExclusive() { g instanceof @ruby_range_dotdotdot }
328+
}
329+
330+
class RangeLiteralSynth extends RangeLiteral, TRangeLiteralSynth {
331+
private boolean inclusive;
332+
333+
RangeLiteralSynth() { this = TRangeLiteralSynth(_, _, inclusive) }
334+
335+
final override Expr getBegin() { result = TIntegerLiteralSynth(this, 0, _) }
336+
337+
final override Expr getEnd() { result = TIntegerLiteralSynth(this, 1, _) }
338+
339+
final override predicate isInclusive() { inclusive = true }
340+
341+
final override predicate isExclusive() { inclusive = false }
342+
}
343+
344+
private string getMethodName(MethodName::Token t) {
345+
result = t.(Ruby::Token).getValue()
346+
or
347+
result = t.(Ruby::Setter).getName().getValue() + "="
348+
}
349+
350+
private class RequiredMethodNameConstantValue extends RequiredConstantValue {
351+
override predicate requiredString(string s) { s = getMethodName(_) }
352+
}
353+
354+
class TokenMethodName extends MethodName, TTokenMethodName {
355+
private MethodName::Token g;
356+
357+
TokenMethodName() { this = TTokenMethodName(g) }
358+
359+
final override ConstantValue::ConstantStringValue getConstantValue() {
360+
result.isString(getMethodName(g))
361+
}
362+
363+
final override string toString() { result = getMethodName(g) }
364+
}

0 commit comments

Comments
 (0)