Skip to content

Commit 502ad3f

Browse files
authored
Merge pull request #247 from github/hmac-jump-to-def
Jump-to-definition
2 parents d1171e0 + 3490e32 commit 502ad3f

File tree

6 files changed

+195
-4
lines changed

6 files changed

+195
-4
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,19 @@ class InstanceVariableAccess extends VariableAccess, TInstanceVariableAccess {
168168
final override string getAPrimaryQlClass() { result = "InstanceVariableAccess" }
169169
}
170170

171+
/** An access to an instance variable where the value is updated. */
172+
class InstanceVariableWriteAccess extends InstanceVariableAccess, VariableWriteAccess { }
173+
174+
/** An access to an instance variable where the value is read. */
175+
class InstanceVariableReadAccess extends InstanceVariableAccess, VariableReadAccess { }
176+
171177
/** An access to a class variable. */
172178
class ClassVariableAccess extends VariableAccess, TClassVariableAccess {
173179
final override string getAPrimaryQlClass() { result = "ClassVariableAccess" }
174180
}
181+
182+
/** An access to a class variable where the value is updated. */
183+
class ClassVariableWriteAccess extends ClassVariableAccess, VariableWriteAccess { }
184+
185+
/** An access to a class variable where the value is read. */
186+
class ClassVariableReadAccess extends ClassVariableAccess, VariableReadAccess { }

ql/lib/codeql/ruby/ast/internal/Module.qll

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,22 @@ private module Cached {
9999
}
100100

101101
/**
102-
* Resolve constant read access (typically a scope expression) to a qualified module name.
102+
* Resolve class or module read access to a qualified module name.
103+
*/
104+
cached
105+
TResolved resolveScopeExpr(ConstantReadAccess r) {
106+
exists(string qname | qname = resolveConstant(r) and result = TResolved(qname))
107+
}
108+
109+
/**
110+
* Resolve constant access (class, module or otherwise) to a qualified module name.
103111
* `resolveScopeExpr/1` picks the best (lowest priority number) result of
104112
* `resolveScopeExpr/2` that resolves to a constant definition. If the constant
105113
* definition is a Namespace then it is returned, if it's a constant assignment then
106114
* the right-hand side of the assignment is resolved.
107115
*/
108116
cached
109-
TResolved resolveScopeExpr(ConstantReadAccess r) {
117+
string resolveConstant(ConstantReadAccess r) {
110118
exists(string qname |
111119
qname =
112120
min(string qn, int p |
@@ -122,11 +130,11 @@ private module Cached {
122130
qn order by p
123131
)
124132
|
125-
result = TResolved(qname)
133+
result = qname
126134
or
127135
exists(ConstantAssignment a |
128136
qname = constantDefinition0(a) and
129-
result = resolveScopeExpr(a.getParent().(Assignment).getRightOperand())
137+
result = resolveConstant(a.getParent().(Assignment).getRightOperand())
130138
)
131139
)
132140
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* @name Definitions
3+
* @description Jump to definition helper query.
4+
* @kind definitions
5+
* @id rb/jump-to-definition
6+
*/
7+
8+
/*
9+
* TODO:
10+
* - should `Foo.new` point to `Foo#initialize`?
11+
*/
12+
13+
import ruby
14+
import codeql.ruby.ast.internal.Module
15+
import codeql.ruby.dataflow.SSA
16+
import codeql.ruby.dataflow.internal.DataFlowDispatch
17+
18+
from DefLoc loc, Expr src, Expr target, string kind
19+
where
20+
ConstantDefLoc(src, target) = loc and kind = "constant"
21+
or
22+
MethodLoc(src, target) = loc and kind = "method"
23+
or
24+
LocalVariableLoc(src, target) = loc and kind = "variable"
25+
or
26+
InstanceVariableLoc(src, target) = loc and kind = "instance variable"
27+
or
28+
ClassVariableLoc(src, target) = loc and kind = "class variable"
29+
select src, target, kind
30+
31+
/**
32+
* Definition location info for different identifiers.
33+
* Each branch holds two values that are subclasses of `Expr`.
34+
* The first is the "source" - some usage of an identifier.
35+
* The second is the "target" - the definition of that identifier.
36+
*/
37+
newtype DefLoc =
38+
/** A constant, module or class. */
39+
ConstantDefLoc(ConstantReadAccess read, ConstantWriteAccess write) { write = definitionOf(read) } or
40+
/** A method call. */
41+
MethodLoc(MethodCall call, Method meth) {
42+
exists(DataFlowCall c | c.getExpr() = call and c.getTarget() = meth)
43+
} or
44+
/** A local variable. */
45+
LocalVariableLoc(VariableReadAccess read, VariableWriteAccess write) {
46+
exists(Ssa::WriteDefinition w |
47+
write = w.getWriteAccess() and
48+
read = w.getARead().getExpr() and
49+
not read.isSynthesized()
50+
)
51+
} or
52+
/** An instance variable */
53+
InstanceVariableLoc(InstanceVariableReadAccess read, InstanceVariableWriteAccess write) {
54+
/*
55+
* We consider instance variables to be "defined" in the initialize method of their enclosing class.
56+
* If that method doesn't exist, we won't provide any jump-to-def information for the instance variable.
57+
*/
58+
59+
exists(Method m |
60+
m.getAChild+() = write and
61+
m.getName() = "initialize" and
62+
write.getVariable() = read.getVariable()
63+
)
64+
} or
65+
/** A class variable */
66+
ClassVariableLoc(ClassVariableReadAccess read, ClassVariableWriteAccess write) {
67+
read.getVariable() = write.getVariable() and
68+
not exists(MethodBase m | m.getAChild+() = write)
69+
}
70+
71+
/**
72+
* Gets the fully qualified name for a constant, based on the context in which it is defined.
73+
*
74+
* For example, given
75+
* ```ruby
76+
* module Foo
77+
* module Bar
78+
* class Baz
79+
* end
80+
* end
81+
* end
82+
* ```
83+
*
84+
* the constant `Baz` has the fully qualified name `Foo::Bar::Baz`.
85+
*/
86+
string constantQualifiedName(ConstantWriteAccess w) {
87+
/* get the qualified name for the parent module, then append w */
88+
exists(ConstantWriteAccess parent | parent = w.getEnclosingModule() |
89+
result = constantQualifiedName(parent) + "::" + w.getName()
90+
)
91+
or
92+
/* base case - there's no parent module */
93+
not exists(ConstantWriteAccess parent | parent = w.getEnclosingModule()) and
94+
result = w.getName()
95+
}
96+
97+
/**
98+
* Gets the constant write that defines the given constant.
99+
* Modules often don't have a unique definition, as they are opened multiple times in different
100+
* files. In these cases we arbitrarily pick the definition with the lexicographically least
101+
* location.
102+
*/
103+
ConstantWriteAccess definitionOf(ConstantReadAccess r) {
104+
result =
105+
min(ConstantWriteAccess w |
106+
constantQualifiedName(w) = resolveConstant(r)
107+
|
108+
w order by w.getLocation().toString()
109+
)
110+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
| Definitions.rb:6:7:6:21 | call to g | Definitions.rb:9:5:11:7 | g | method |
2+
| Definitions.rb:6:9:6:21 | SOME_CONSTANT | Definitions.rb:2:3:2:15 | SOME_CONSTANT | constant |
3+
| Definitions.rb:10:7:10:7 | x | Definitions.rb:9:11:9:11 | x | variable |
4+
| Definitions.rb:14:7:14:7 | call to f | Definitions.rb:5:5:7:7 | f | method |
5+
| Definitions.rb:23:5:23:7 | @@a | Definitions.rb:20:3:20:5 | @@a | class variable |
6+
| Definitions.rb:32:7:32:7 | y | Definitions.rb:31:10:31:10 | y | variable |
7+
| Definitions.rb:36:7:36:7 | A | Definitions.rb:1:1:17:3 | A | constant |
8+
| Definitions.rb:36:7:36:10 | B | Definitions.rb:4:3:16:5 | B | constant |
9+
| Definitions.rb:36:7:36:18 | call to g | Definitions.rb:9:5:11:7 | g | method |
10+
| Definitions.rb:36:18:36:18 | y | Definitions.rb:35:11:35:11 | y | variable |
11+
| Definitions.rb:39:7:39:8 | @e | Definitions.rb:30:7:30:8 | @e | instance variable |
12+
| Definitions.rb:41:7:41:9 | @@b | Definitions.rb:27:5:27:7 | @@b | class variable |
13+
| Definitions.rb:46:1:46:1 | C | Definitions.rb:19:1:44:3 | C | constant |
14+
| Definitions.rb:46:1:46:4 | D | Definitions.rb:26:3:43:5 | D | constant |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/analysis/Definitions.ql
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
module A
2+
SOME_CONSTANT = 1
3+
4+
class B
5+
def f
6+
g SOME_CONSTANT
7+
end
8+
9+
def g x
10+
x
11+
end
12+
13+
def h
14+
f
15+
end
16+
end
17+
end
18+
19+
module C
20+
@@a = 1
21+
22+
def self.a
23+
@@a
24+
end
25+
26+
class D
27+
@@b = 2
28+
29+
def initialize
30+
@e = 1
31+
x, y = [1, 2]
32+
y
33+
end
34+
35+
def h y
36+
A::B.new.g y
37+
UnknownClass.some_method
38+
@f = 2
39+
@e
40+
@f
41+
@@b
42+
end
43+
end
44+
end
45+
46+
C::D.new

0 commit comments

Comments
 (0)