Skip to content

Commit abb37e2

Browse files
authored
Merge pull request #165 from github/aibaars/methods
Implement method lookup
2 parents 3590a2c + 549e5ab commit abb37e2

17 files changed

Lines changed: 614 additions & 49 deletions

File tree

ql/src/codeql_ruby/ast/Call.qll

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,21 @@ class MethodCall extends Call, TMethodCall {
8585
*/
8686
Expr getReceiver() { none() }
8787

88+
/**
89+
* Holds if the receiver is `self` or there is no receiver, which has the same
90+
* meaning as an explict `self`. For example:
91+
*
92+
* ```rb
93+
* self.foo
94+
* foo
95+
* ```
96+
*/
97+
predicate receiverIsSelf() {
98+
this.getReceiver() instanceof Self
99+
or
100+
not exists(this.getReceiver())
101+
}
102+
88103
/**
89104
* Gets the name of the method being called. For example, in:
90105
*

ql/src/codeql_ruby/ast/Module.qll

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,18 @@ private import internal.TreeSitter
88
* A representation of a run-time `module` or `class` value.
99
*/
1010
class Module extends TModule {
11-
/** Get a declaration of this module, if any. */
11+
/** Gets a declaration of this module, if any. */
1212
ModuleBase getADeclaration() { result.getModule() = this }
1313

14+
/** Gets the super class of this module, if any. */
15+
Module getSuperClass() { result = getSuperClass(this) }
16+
17+
/** Gets a `prepend`ed module. */
18+
Module getAPrependedModule() { result = getAPrependedModule(this) }
19+
20+
/** Gets an `include`d module. */
21+
Module getAnIncludedModule() { result = getAnIncludedModule(this) }
22+
1423
/** Gets a textual representation of this module. */
1524
string toString() {
1625
this = TResolved(result)

ql/src/codeql_ruby/ast/internal/Module.qll

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,58 @@ private module Cached {
3434
result = scopeAppend(container, n.getName())
3535
)
3636
}
37+
38+
cached
39+
Module getSuperClass(Module cls) {
40+
cls = TResolved("Object") and result = TResolved("BasicObject")
41+
or
42+
cls = TResolved("Module") and result = TResolved("Object")
43+
or
44+
cls = TResolved("Class") and result = TResolved("Module")
45+
or
46+
not cls = TResolved(builtin()) and
47+
(
48+
exists(ClassDeclaration d |
49+
d = cls.getADeclaration() and
50+
result = resolveScopeExpr(d.getSuperclassExpr())
51+
)
52+
or
53+
result = TResolved("Object") and
54+
forex(ClassDeclaration d | d = cls.getADeclaration() |
55+
not exists(resolveScopeExpr(d.getSuperclassExpr()))
56+
)
57+
)
58+
}
59+
60+
cached
61+
Module getAnIncludedModule(Module m) {
62+
m = TResolved("Object") and result = TResolved("Kernel")
63+
or
64+
exists(IncludeOrPrependCall c |
65+
c.getMethodName() = "include" and
66+
(
67+
m = resolveScopeExpr(c.getReceiver())
68+
or
69+
m = enclosingModule(c).getModule() and
70+
c.receiverIsSelf()
71+
) and
72+
result = resolveScopeExpr(c.getAnArgument())
73+
)
74+
}
75+
76+
cached
77+
Module getAPrependedModule(Module m) {
78+
exists(IncludeOrPrependCall c |
79+
c.getMethodName() = "prepend" and
80+
(
81+
m = resolveScopeExpr(c.getReceiver())
82+
or
83+
m = enclosingModule(c).getModule() and
84+
c.receiverIsSelf()
85+
) and
86+
result = resolveScopeExpr(c.getAnArgument())
87+
)
88+
}
3789
}
3890

3991
import Cached
@@ -242,3 +294,31 @@ private string qualifiedModuleName(string container, string name) {
242294
container = "Object" and name = result
243295
)
244296
}
297+
298+
private Module getAncestors(Module m) {
299+
result = m or
300+
result = getAncestors(m.getAnIncludedModule()) or
301+
result = getAncestors(m.getAPrependedModule())
302+
}
303+
304+
Method getMethod(TModule owner, string name) {
305+
exists(ModuleBase m | m.getModule() = owner and result = m.getMethod(name))
306+
}
307+
308+
private Method lookupMethod0(Module m, string name) {
309+
result = lookupMethod0(m.getAPrependedModule(), name)
310+
or
311+
not exists(getMethod(getAncestors(m.getAPrependedModule()), name)) and
312+
(
313+
result = getMethod(m, name)
314+
or
315+
not exists(getMethod(m, name)) and result = lookupMethod0(m.getAnIncludedModule(), name)
316+
)
317+
}
318+
319+
Method lookupMethod(Module m, string name) {
320+
result = lookupMethod0(m, name)
321+
or
322+
not exists(lookupMethod0(m, name)) and
323+
result = lookupMethod(m.getSuperClass(), name)
324+
}

ql/src/codeql_ruby/controlflow/internal/Cfg.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/**
22
* @kind graph
3+
* @id rb/test/cfg
34
*/
45

56
import codeql_ruby.CFG

ql/test/library-tests/ast/Ast.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/**
22
* @kind graph
3+
* @id rb/test/print-ast
34
*/
45

56
import codeql_ruby.printAst

ql/test/library-tests/ast/modules/modules.expected

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -101,49 +101,3 @@ modulesInModules
101101
| modules.rb:95:1:99:3 | IncludeTest2 | modules.rb:97:3:98:5 | Z | Z |
102102
| modules.rb:101:1:105:3 | PrependTest | modules.rb:103:3:104:5 | Y | Y |
103103
| modules.rb:107:1:110:3 | MM | modules.rb:108:3:109:5 | MM | MM |
104-
moduleTypes
105-
| classes.rb:2:1:56:3 | classes.rb | file://:0:0:0:0 | Object |
106-
| classes.rb:3:1:4:3 | Foo | modules.rb:4:1:24:3 | Foo |
107-
| classes.rb:7:1:8:3 | Bar | modules.rb:37:1:46:3 | Bar |
108-
| classes.rb:11:1:12:3 | Baz | classes.rb:11:1:12:3 | Baz |
109-
| classes.rb:15:1:15:20 | MyModule | classes.rb:15:1:15:20 | MyModule |
110-
| classes.rb:16:1:17:3 | MyClass | classes.rb:16:1:17:3 | MyModule::MyClass |
111-
| classes.rb:20:1:37:3 | Wibble | classes.rb:20:1:37:3 | Wibble |
112-
| classes.rb:32:3:33:5 | ClassInWibble | classes.rb:32:3:33:5 | Wibble::ClassInWibble |
113-
| classes.rb:35:3:36:5 | ModuleInWibble | classes.rb:35:3:36:5 | Wibble::ModuleInWibble |
114-
| classes.rb:55:1:56:3 | MyClassInGlobalScope | classes.rb:55:1:56:3 | MyClassInGlobalScope |
115-
| modules.rb:1:1:2:3 | Empty | modules.rb:1:1:2:3 | Empty |
116-
| modules.rb:1:1:122:1 | modules.rb | file://:0:0:0:0 | Object |
117-
| modules.rb:4:1:24:3 | Foo | modules.rb:4:1:24:3 | Foo |
118-
| modules.rb:5:3:14:5 | Bar | modules.rb:5:3:14:5 | Foo::Bar |
119-
| modules.rb:6:5:7:7 | ClassInFooBar | modules.rb:6:5:7:7 | Foo::Bar::ClassInFooBar |
120-
| modules.rb:19:3:20:5 | ClassInFoo | modules.rb:19:3:20:5 | Foo::ClassInFoo |
121-
| modules.rb:26:1:35:3 | Foo | modules.rb:4:1:24:3 | Foo |
122-
| modules.rb:30:3:31:5 | ClassInAnotherDefinitionOfFoo | modules.rb:30:3:31:5 | Foo::ClassInAnotherDefinitionOfFoo |
123-
| modules.rb:37:1:46:3 | Bar | modules.rb:37:1:46:3 | Bar |
124-
| modules.rb:48:1:57:3 | Bar | modules.rb:5:3:14:5 | Foo::Bar |
125-
| modules.rb:49:3:50:5 | ClassInAnotherDefinitionOfFooBar | modules.rb:49:3:50:5 | Foo::Bar::ClassInAnotherDefinitionOfFooBar |
126-
| modules.rb:60:1:61:3 | MyModuleInGlobalScope | modules.rb:60:1:61:3 | MyModuleInGlobalScope |
127-
| modules.rb:63:1:81:3 | Test | modules.rb:63:1:81:3 | Test |
128-
| modules.rb:65:3:68:5 | Foo1 | modules.rb:65:3:68:5 | Test::Foo1 |
129-
| modules.rb:66:5:67:7 | Bar | modules.rb:66:5:67:7 | Test::Foo1::Bar |
130-
| modules.rb:70:3:74:5 | Foo2 | modules.rb:70:3:74:5 | Test::Foo2 |
131-
| modules.rb:71:5:71:19 | Foo2 | modules.rb:71:5:71:19 | Test::Foo2::Foo2 |
132-
| modules.rb:72:5:73:7 | Bar | modules.rb:72:5:73:7 | Test::Foo2::Foo2::Bar |
133-
| modules.rb:76:3:80:5 | Foo3 | modules.rb:76:3:80:5 | Test::Foo3 |
134-
| modules.rb:78:5:79:7 | Bar | modules.rb:37:1:46:3 | Bar |
135-
| modules.rb:83:1:86:3 | Other | modules.rb:83:1:86:3 | Other |
136-
| modules.rb:84:3:85:5 | Foo1 | modules.rb:84:3:85:5 | Other::Foo1 |
137-
| modules.rb:88:1:93:3 | IncludeTest | modules.rb:88:1:93:3 | IncludeTest |
138-
| modules.rb:91:3:92:5 | Y | modules.rb:91:3:92:5 | Test::Foo1::Y |
139-
| modules.rb:95:1:99:3 | IncludeTest2 | modules.rb:95:1:99:3 | IncludeTest2 |
140-
| modules.rb:97:3:98:5 | Z | modules.rb:97:3:98:5 | Test::Foo1::Z |
141-
| modules.rb:101:1:105:3 | PrependTest | modules.rb:101:1:105:3 | PrependTest |
142-
| modules.rb:103:3:104:5 | Y | modules.rb:103:3:104:5 | Test::Foo2::Y |
143-
| modules.rb:107:1:110:3 | MM | modules.rb:107:1:110:3 | MM |
144-
| modules.rb:108:3:109:5 | MM | modules.rb:108:3:109:5 | MM::MM |
145-
| modules.rb:112:1:113:3 | YY | modules.rb:112:1:113:3 | YY |
146-
| modules.rb:115:1:118:3 | XX | modules.rb:115:1:118:3 | XX |
147-
| modules.rb:116:7:117:9 | YY | modules.rb:116:7:117:9 | XX::YY |
148-
| modules.rb:120:1:121:3 | Baz | modules.rb:120:1:121:3 | Test::Foo1::Bar::Baz |
149-
| toplevel.rb:1:1:5:23 | toplevel.rb | file://:0:0:0:0 | Object |

ql/test/library-tests/ast/modules/modules.ql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,3 @@ query predicate classesInModules(ModuleDeclaration mod, ClassDeclaration klass,
2323
query predicate modulesInModules(ModuleDeclaration mod, ModuleDeclaration child, string name) {
2424
child = mod.getModule(name)
2525
}
26-
27-
query predicate moduleTypes(ModuleBase def, Module type) { type = def.getModule() }
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#-----| Object
2+
#-----| include -> Kernel
3+
#-----| super -> BasicObject
4+
5+
#-----| Class
6+
#-----| super -> Module
7+
8+
#-----| Kernel
9+
10+
#-----| Module
11+
#-----| super -> Object
12+
13+
#-----| BasicObject
14+
15+
hello.rb:
16+
# 1| EnglishWords
17+
18+
# 11| Greeting
19+
#-----| super -> Object
20+
#-----| include -> EnglishWords
21+
22+
# 18| HelloWorld
23+
#-----| super -> Greeting
24+
25+
modules.rb:
26+
# 1| Empty
27+
28+
# 4| Foo
29+
30+
# 37| Bar
31+
#-----| super -> Object
32+
33+
# 60| MyModuleInGlobalScope
34+
35+
# 63| Test
36+
37+
# 83| Other
38+
39+
# 88| IncludeTest
40+
#-----| include -> Test
41+
42+
# 95| IncludeTest2
43+
#-----| include -> Test
44+
45+
# 101| PrependTest
46+
#-----| prepend -> Test
47+
48+
# 107| MM
49+
50+
# 112| YY
51+
#-----| super -> Object
52+
53+
# 115| XX
54+
55+
# 5| Foo::Bar
56+
57+
# 19| Foo::ClassInFoo
58+
#-----| super -> Object
59+
60+
# 30| Foo::ClassInAnotherDefinitionOfFoo
61+
#-----| super -> Object
62+
63+
# 65| Test::Foo1
64+
65+
# 70| Test::Foo2
66+
67+
# 76| Test::Foo3
68+
69+
# 84| Other::Foo1
70+
71+
# 116| XX::YY
72+
#-----| super -> YY
73+
74+
# 6| Foo::Bar::ClassInFooBar
75+
#-----| super -> Object
76+
77+
# 71| Test::Foo2::Foo2
78+
79+
# 108| MM::MM
80+
81+
# 49| Foo::Bar::ClassInAnotherDefinitionOfFooBar
82+
#-----| super -> Object
83+
84+
# 66| Test::Foo1::Bar
85+
#-----| super -> Object
86+
87+
# 91| Test::Foo1::Y
88+
89+
# 97| Test::Foo1::Z
90+
91+
# 103| Test::Foo2::Y
92+
93+
# 72| Test::Foo2::Foo2::Bar
94+
#-----| super -> Object
95+
96+
# 120| Test::Foo1::Bar::Baz
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @kind graph
3+
* @id rb/test/ancestors
4+
*/
5+
6+
import ruby
7+
8+
query predicate nodes(Module node, string key, string value) {
9+
key = "semmle.label" and value = node.toString()
10+
}
11+
12+
query predicate edges(Module source, Module target, string key, string value) {
13+
key = "semmle.label" and
14+
(
15+
target = source.getSuperClass() and value = "super"
16+
or
17+
target = source.getAPrependedModule() and value = "prepend"
18+
or
19+
target = source.getAnIncludedModule() and value = "include"
20+
)
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module EnglishWords
2+
def hello
3+
return "hello"
4+
end
5+
def world
6+
return "world"
7+
end
8+
end
9+
10+
11+
class Greeting
12+
include EnglishWords
13+
def message
14+
return hello
15+
end
16+
end
17+
18+
class HelloWorld < Greeting
19+
def message
20+
return super + " " + world + "!"
21+
end
22+
end

0 commit comments

Comments
 (0)