Skip to content

Commit 858ca0b

Browse files
authored
Merge pull request #100 from github/call_ast
Add AST classes and tests for method calls
2 parents 08c655e + 243dfde commit 858ca0b

File tree

21 files changed

+1712
-677
lines changed

21 files changed

+1712
-677
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extractor/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ edition = "2018"
1010
flate2 = "1.0"
1111
node-types = { path = "../node-types" }
1212
tree-sitter = "0.17"
13-
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "5021a6a6eda24e10f954dcfec00e7a7adafba8ba" }
13+
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "454f90628d1d14d0953b3be78bdb9a09f7a5bfd7" }
1414
clap = "2.33"
1515
tracing = "0.1"
1616
tracing-subscriber = { version = "0.2", features = ["env-filter"] }

generator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ edition = "2018"
1010
node-types = { path = "../node-types" }
1111
tracing = "0.1"
1212
tracing-subscriber = { version = "0.2", features = ["env-filter"] }
13-
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "5021a6a6eda24e10f954dcfec00e7a7adafba8ba" }
13+
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "454f90628d1d14d0953b3be78bdb9a09f7a5bfd7" }

ql/src/codeql_ruby/AST.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import codeql.Locations
2+
import ast.Call
23
import ast.Control
34
import ast.Expr
45
import ast.Method

ql/src/codeql_ruby/ast/Call.qll

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
private import codeql_ruby.AST
2+
private import internal.Call
3+
4+
/**
5+
* A call.
6+
*/
7+
class Call extends Expr {
8+
override Call::Range range;
9+
10+
override string getAPrimaryQlClass() { result = "Call" }
11+
12+
final override string toString() { result = "call to " + this.getMethodName() }
13+
14+
/**
15+
* Gets the receiver of this call, if any. For example:
16+
* ```rb
17+
* foo.bar
18+
* baz()
19+
* ```
20+
* The result for the call to `bar` is the `Expr` for `foo`, while the call
21+
* to `baz` has no result.
22+
*/
23+
final Expr getReceiver() { result = range.getReceiver() }
24+
25+
/**
26+
* Gets the name of the method being called. For example, in:
27+
* ```rb
28+
* foo.bar x, y
29+
* ```
30+
* the result is `"bar"`.
31+
*
32+
* N.B. in the following example, where the method name is a scope
33+
* resolution, the result is the name being resolved, i.e. `"bar"`. Use
34+
* `getMethodScopeResolution` to get the complete `ScopeResolution`.
35+
* ```rb
36+
* Foo::bar x, y
37+
* ```
38+
*/
39+
final string getMethodName() { result = range.getMethodName() }
40+
41+
/**
42+
* Gets the scope resolution of this call, if any. In the following example,
43+
* the result is the `ScopeResolution` for `Foo::bar`, while
44+
* `getMethodName()` returns `"bar"`.
45+
* ```rb
46+
* Foo::bar()
47+
* ```
48+
*/
49+
final ScopeResolution getMethodScopeResolution() { result = range.getMethodScopeResolution() }
50+
51+
/**
52+
* Gets the `n`th argument of this method call. In the following example, the
53+
* result for n=0 is the `IntegerLiteral` 0, while for n=1 the result is a
54+
* `Pair` (whose `getKey` returns the `SymbolLiteral` for `bar`, and
55+
* `getValue` returns the `IntegerLiteral` 1). Keyword arguments like this
56+
* can be accessed more naturally using the
57+
* `getKeywordArgument(string keyword)` predicate.
58+
* ```rb
59+
* foo(0, bar: 1)
60+
* ```
61+
*/
62+
final Expr getArgument(int n) { result = range.getArgument(n) }
63+
64+
/**
65+
* Gets an argument of this method call.
66+
*/
67+
final Expr getAnArgument() { result = this.getArgument(_) }
68+
69+
/**
70+
* Gets the value of the keyword argument whose key is `keyword`, if any. For
71+
* example, the result for `getKeywordArgument("qux")` in the following
72+
* example is the `IntegerLiteral` 123.
73+
* ```rb
74+
* foo :bar "baz", qux: 123
75+
* ```
76+
*/
77+
final Expr getKeywordArgument(string keyword) {
78+
exists(Pair p |
79+
p = this.getAnArgument() and
80+
p.getKey().(SymbolLiteral).getValueText() = keyword and
81+
result = p.getValue()
82+
)
83+
}
84+
85+
/**
86+
* Gets the number of arguments of this method call.
87+
*/
88+
final int getNumberOfArguments() { result = count(this.getAnArgument()) }
89+
90+
/**
91+
* Gets the block of this method call, if any.
92+
* ```rb
93+
* foo.each { |x| puts x }
94+
* ```
95+
*/
96+
final Block getBlock() { result = range.getBlock() }
97+
}
98+
99+
/**
100+
* A call to `yield`.
101+
* ```rb
102+
* yield x, y
103+
* ```
104+
*/
105+
class YieldCall extends Call, @yield {
106+
final override YieldCall::Range range;
107+
108+
final override string getAPrimaryQlClass() { result = "YieldCall" }
109+
}
110+
111+
/**
112+
* A block argument in a method call.
113+
* ```rb
114+
* foo(&block)
115+
* ```
116+
*/
117+
class BlockArgument extends Expr, @block_argument {
118+
final override BlockArgument::Range range;
119+
120+
final override string getAPrimaryQlClass() { result = "BlockArgument" }
121+
122+
final override string toString() { result = "&..." }
123+
124+
/**
125+
* Gets the underlying expression representing the block. In the following
126+
* example, the result is the `Expr` for `bar`:
127+
* ```rb
128+
* foo(&bar)
129+
* ```
130+
*/
131+
final Expr getExpr() { result = range.getExpr() }
132+
}
133+
134+
/**
135+
* A splat argument in a method call.
136+
* ```rb
137+
* foo(*args)
138+
* ```
139+
*/
140+
class SplatArgument extends Expr, @splat_argument {
141+
final override SplatArgument::Range range;
142+
143+
final override string getAPrimaryQlClass() { result = "SplatArgument" }
144+
145+
final override string toString() { result = "*..." }
146+
147+
/**
148+
* Gets the underlying expression. In the following example, the result is
149+
* the `Expr` for `bar`:
150+
* ```rb
151+
* foo(*bar)
152+
* ```
153+
*/
154+
final Expr getExpr() { result = range.getExpr() }
155+
}
156+
157+
/**
158+
* A hash-splat (or 'double-splat') argument in a method call.
159+
* ```rb
160+
* foo(**options)
161+
* ```
162+
*/
163+
class HashSplatArgument extends Expr, @hash_splat_argument {
164+
final override HashSplatArgument::Range range;
165+
166+
final override string getAPrimaryQlClass() { result = "HashSplatArgument" }
167+
168+
final override string toString() { result = "**..." }
169+
170+
/**
171+
* Gets the underlying expression. In the following example, the result is
172+
* the `Expr` for `bar`:
173+
* ```rb
174+
* foo(**bar)
175+
* ```
176+
*/
177+
final Expr getExpr() { result = range.getExpr() }
178+
}

ql/src/codeql_ruby/ast/Expr.qll

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ class Expr extends AstNode {
2020
class Literal extends Expr {
2121
override Literal::Range range;
2222

23-
override string toString() { result = this.getValueText() }
23+
override string toString() { result = range.toString() }
2424

25-
/** Gets the source text for this literal. */
25+
/** Gets the source text for this literal, if it is constant. */
2626
final string getValueText() { result = range.getValueText() }
2727
}
2828

@@ -74,6 +74,35 @@ class RegexLiteral extends Literal, @regex {
7474
final override string getAPrimaryQlClass() { result = "RegexLiteral" }
7575
}
7676

77+
/**
78+
* A string literal.
79+
* ```rb
80+
* 'hello'
81+
* "hello, #{name}"
82+
* ```
83+
* TODO: expand this minimal placeholder.
84+
*/
85+
class StringLiteral extends Literal, @string__ {
86+
final override StringLiteral::Range range;
87+
88+
final override string getAPrimaryQlClass() { result = "StringLiteral" }
89+
}
90+
91+
/**
92+
* A symbol literal.
93+
* ```rb
94+
* :foo
95+
* :"foo bar"
96+
* :"foo bar #{baz}"
97+
* ```
98+
* TODO: expand this minimal placeholder.
99+
*/
100+
class SymbolLiteral extends Literal {
101+
final override SymbolLiteral::Range range;
102+
103+
final override string getAPrimaryQlClass() { result = "SymbolLiteral" }
104+
}
105+
77106
/** A sequence of expressions. */
78107
class ExprSequence extends Expr {
79108
override ExprSequence::Range range;
@@ -97,3 +126,78 @@ class ExprSequence extends Expr {
97126
/** Holds if this sequence has no expressions. */
98127
final predicate isEmpty() { this.getNumberOfExpressions() = 0 }
99128
}
129+
130+
/**
131+
* A scope resolution, typically used to access constants defined in a class or
132+
* module.
133+
* ```rb
134+
* Foo::Bar
135+
* ```
136+
*/
137+
class ScopeResolution extends Expr, @scope_resolution {
138+
final override ScopeResolution::Range range;
139+
140+
final override string getAPrimaryQlClass() { result = "ScopeResolution" }
141+
142+
final override string toString() { result = "...::" + this.getName() }
143+
144+
/**
145+
* Gets the expression representing the scope, if any. In the following
146+
* example, the scope is the `Expr` for `Foo`:
147+
* ```rb
148+
* Foo::Bar
149+
* ```
150+
* However, in the following example, accessing the `Bar` constant in the
151+
* `Object` class, there is no result:
152+
* ```rb
153+
* ::Bar
154+
* ```
155+
*/
156+
final Expr getScope() { result = range.getScope() }
157+
158+
/**
159+
* Gets the name being resolved. For example, in `Foo::Bar`, the result is
160+
* `"Bar"`.
161+
*/
162+
final string getName() { result = range.getName() }
163+
}
164+
165+
/**
166+
* A pair expression. For example, in a hash:
167+
* ```rb
168+
* { foo: bar }
169+
* ```
170+
* Or a keyword argument:
171+
* ```rb
172+
* baz(qux: 1)
173+
* ```
174+
*/
175+
class Pair extends Expr, @pair {
176+
final override Pair::Range range;
177+
178+
final override string getAPrimaryQlClass() { result = "Pair" }
179+
180+
final override string toString() { result = "Pair" }
181+
182+
/**
183+
* Gets the key expression of this pair. For example, the `SymbolLiteral`
184+
* representing the keyword `foo` in the following example:
185+
* ```rb
186+
* bar(foo: 123)
187+
* ```
188+
* Or the `StringLiteral` for `'foo'` in the following hash pair:
189+
* ```rb
190+
* { 'foo' => 123 }
191+
* ```
192+
*/
193+
final Expr getKey() { result = range.getKey() }
194+
195+
/**
196+
* Gets the value expression of this pair. For example, the `InteralLiteral`
197+
* 123 in the following hash pair:
198+
* ```rb
199+
* { 'foo' => 123 }
200+
* ```
201+
*/
202+
final Expr getValue() { result = range.getValue() }
203+
}

ql/src/codeql_ruby/ast/Method.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class DoBlock extends Block, @do_block {
8080

8181
final override string getAPrimaryQlClass() { result = "DoBlock" }
8282

83-
final override string toString() { result = "| ... |" }
83+
final override string toString() { result = "do ... end" }
8484
}
8585

8686
/**

0 commit comments

Comments
 (0)