mirror of
https://github.com/github/codeql.git
synced 2026-04-25 00:35:20 +02:00
Merge pull request #247 from github/hmac-jump-to-def
Jump-to-definition
This commit is contained in:
@@ -168,7 +168,19 @@ class InstanceVariableAccess extends VariableAccess, TInstanceVariableAccess {
|
||||
final override string getAPrimaryQlClass() { result = "InstanceVariableAccess" }
|
||||
}
|
||||
|
||||
/** An access to an instance variable where the value is updated. */
|
||||
class InstanceVariableWriteAccess extends InstanceVariableAccess, VariableWriteAccess { }
|
||||
|
||||
/** An access to an instance variable where the value is read. */
|
||||
class InstanceVariableReadAccess extends InstanceVariableAccess, VariableReadAccess { }
|
||||
|
||||
/** An access to a class variable. */
|
||||
class ClassVariableAccess extends VariableAccess, TClassVariableAccess {
|
||||
final override string getAPrimaryQlClass() { result = "ClassVariableAccess" }
|
||||
}
|
||||
|
||||
/** An access to a class variable where the value is updated. */
|
||||
class ClassVariableWriteAccess extends ClassVariableAccess, VariableWriteAccess { }
|
||||
|
||||
/** An access to a class variable where the value is read. */
|
||||
class ClassVariableReadAccess extends ClassVariableAccess, VariableReadAccess { }
|
||||
|
||||
@@ -99,14 +99,22 @@ private module Cached {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve constant read access (typically a scope expression) to a qualified module name.
|
||||
* Resolve class or module read access to a qualified module name.
|
||||
*/
|
||||
cached
|
||||
TResolved resolveScopeExpr(ConstantReadAccess r) {
|
||||
exists(string qname | qname = resolveConstant(r) and result = TResolved(qname))
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve constant access (class, module or otherwise) to a qualified module name.
|
||||
* `resolveScopeExpr/1` picks the best (lowest priority number) result of
|
||||
* `resolveScopeExpr/2` that resolves to a constant definition. If the constant
|
||||
* definition is a Namespace then it is returned, if it's a constant assignment then
|
||||
* the right-hand side of the assignment is resolved.
|
||||
*/
|
||||
cached
|
||||
TResolved resolveScopeExpr(ConstantReadAccess r) {
|
||||
string resolveConstant(ConstantReadAccess r) {
|
||||
exists(string qname |
|
||||
qname =
|
||||
min(string qn, int p |
|
||||
@@ -122,11 +130,11 @@ private module Cached {
|
||||
qn order by p
|
||||
)
|
||||
|
|
||||
result = TResolved(qname)
|
||||
result = qname
|
||||
or
|
||||
exists(ConstantAssignment a |
|
||||
qname = constantDefinition0(a) and
|
||||
result = resolveScopeExpr(a.getParent().(Assignment).getRightOperand())
|
||||
result = resolveConstant(a.getParent().(Assignment).getRightOperand())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
110
ql/src/queries/analysis/Definitions.ql
Normal file
110
ql/src/queries/analysis/Definitions.ql
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @name Definitions
|
||||
* @description Jump to definition helper query.
|
||||
* @kind definitions
|
||||
* @id rb/jump-to-definition
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* - should `Foo.new` point to `Foo#initialize`?
|
||||
*/
|
||||
|
||||
import ruby
|
||||
import codeql.ruby.ast.internal.Module
|
||||
import codeql.ruby.dataflow.SSA
|
||||
import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
|
||||
from DefLoc loc, Expr src, Expr target, string kind
|
||||
where
|
||||
ConstantDefLoc(src, target) = loc and kind = "constant"
|
||||
or
|
||||
MethodLoc(src, target) = loc and kind = "method"
|
||||
or
|
||||
LocalVariableLoc(src, target) = loc and kind = "variable"
|
||||
or
|
||||
InstanceVariableLoc(src, target) = loc and kind = "instance variable"
|
||||
or
|
||||
ClassVariableLoc(src, target) = loc and kind = "class variable"
|
||||
select src, target, kind
|
||||
|
||||
/**
|
||||
* Definition location info for different identifiers.
|
||||
* Each branch holds two values that are subclasses of `Expr`.
|
||||
* The first is the "source" - some usage of an identifier.
|
||||
* The second is the "target" - the definition of that identifier.
|
||||
*/
|
||||
newtype DefLoc =
|
||||
/** A constant, module or class. */
|
||||
ConstantDefLoc(ConstantReadAccess read, ConstantWriteAccess write) { write = definitionOf(read) } or
|
||||
/** A method call. */
|
||||
MethodLoc(MethodCall call, Method meth) {
|
||||
exists(DataFlowCall c | c.getExpr() = call and c.getTarget() = meth)
|
||||
} or
|
||||
/** A local variable. */
|
||||
LocalVariableLoc(VariableReadAccess read, VariableWriteAccess write) {
|
||||
exists(Ssa::WriteDefinition w |
|
||||
write = w.getWriteAccess() and
|
||||
read = w.getARead().getExpr() and
|
||||
not read.isSynthesized()
|
||||
)
|
||||
} or
|
||||
/** An instance variable */
|
||||
InstanceVariableLoc(InstanceVariableReadAccess read, InstanceVariableWriteAccess write) {
|
||||
/*
|
||||
* We consider instance variables to be "defined" in the initialize method of their enclosing class.
|
||||
* If that method doesn't exist, we won't provide any jump-to-def information for the instance variable.
|
||||
*/
|
||||
|
||||
exists(Method m |
|
||||
m.getAChild+() = write and
|
||||
m.getName() = "initialize" and
|
||||
write.getVariable() = read.getVariable()
|
||||
)
|
||||
} or
|
||||
/** A class variable */
|
||||
ClassVariableLoc(ClassVariableReadAccess read, ClassVariableWriteAccess write) {
|
||||
read.getVariable() = write.getVariable() and
|
||||
not exists(MethodBase m | m.getAChild+() = write)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fully qualified name for a constant, based on the context in which it is defined.
|
||||
*
|
||||
* For example, given
|
||||
* ```ruby
|
||||
* module Foo
|
||||
* module Bar
|
||||
* class Baz
|
||||
* end
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* the constant `Baz` has the fully qualified name `Foo::Bar::Baz`.
|
||||
*/
|
||||
string constantQualifiedName(ConstantWriteAccess w) {
|
||||
/* get the qualified name for the parent module, then append w */
|
||||
exists(ConstantWriteAccess parent | parent = w.getEnclosingModule() |
|
||||
result = constantQualifiedName(parent) + "::" + w.getName()
|
||||
)
|
||||
or
|
||||
/* base case - there's no parent module */
|
||||
not exists(ConstantWriteAccess parent | parent = w.getEnclosingModule()) and
|
||||
result = w.getName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constant write that defines the given constant.
|
||||
* Modules often don't have a unique definition, as they are opened multiple times in different
|
||||
* files. In these cases we arbitrarily pick the definition with the lexicographically least
|
||||
* location.
|
||||
*/
|
||||
ConstantWriteAccess definitionOf(ConstantReadAccess r) {
|
||||
result =
|
||||
min(ConstantWriteAccess w |
|
||||
constantQualifiedName(w) = resolveConstant(r)
|
||||
|
|
||||
w order by w.getLocation().toString()
|
||||
)
|
||||
}
|
||||
14
ql/test/query-tests/analysis/Definitions.expected
Normal file
14
ql/test/query-tests/analysis/Definitions.expected
Normal file
@@ -0,0 +1,14 @@
|
||||
| Definitions.rb:6:7:6:21 | call to g | Definitions.rb:9:5:11:7 | g | method |
|
||||
| Definitions.rb:6:9:6:21 | SOME_CONSTANT | Definitions.rb:2:3:2:15 | SOME_CONSTANT | constant |
|
||||
| Definitions.rb:10:7:10:7 | x | Definitions.rb:9:11:9:11 | x | variable |
|
||||
| Definitions.rb:14:7:14:7 | call to f | Definitions.rb:5:5:7:7 | f | method |
|
||||
| Definitions.rb:23:5:23:7 | @@a | Definitions.rb:20:3:20:5 | @@a | class variable |
|
||||
| Definitions.rb:32:7:32:7 | y | Definitions.rb:31:10:31:10 | y | variable |
|
||||
| Definitions.rb:36:7:36:7 | A | Definitions.rb:1:1:17:3 | A | constant |
|
||||
| Definitions.rb:36:7:36:10 | B | Definitions.rb:4:3:16:5 | B | constant |
|
||||
| Definitions.rb:36:7:36:18 | call to g | Definitions.rb:9:5:11:7 | g | method |
|
||||
| Definitions.rb:36:18:36:18 | y | Definitions.rb:35:11:35:11 | y | variable |
|
||||
| Definitions.rb:39:7:39:8 | @e | Definitions.rb:30:7:30:8 | @e | instance variable |
|
||||
| Definitions.rb:41:7:41:9 | @@b | Definitions.rb:27:5:27:7 | @@b | class variable |
|
||||
| Definitions.rb:46:1:46:1 | C | Definitions.rb:19:1:44:3 | C | constant |
|
||||
| Definitions.rb:46:1:46:4 | D | Definitions.rb:26:3:43:5 | D | constant |
|
||||
1
ql/test/query-tests/analysis/Definitions.qlref
Normal file
1
ql/test/query-tests/analysis/Definitions.qlref
Normal file
@@ -0,0 +1 @@
|
||||
queries/analysis/Definitions.ql
|
||||
46
ql/test/query-tests/analysis/Definitions.rb
Normal file
46
ql/test/query-tests/analysis/Definitions.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
module A
|
||||
SOME_CONSTANT = 1
|
||||
|
||||
class B
|
||||
def f
|
||||
g SOME_CONSTANT
|
||||
end
|
||||
|
||||
def g x
|
||||
x
|
||||
end
|
||||
|
||||
def h
|
||||
f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module C
|
||||
@@a = 1
|
||||
|
||||
def self.a
|
||||
@@a
|
||||
end
|
||||
|
||||
class D
|
||||
@@b = 2
|
||||
|
||||
def initialize
|
||||
@e = 1
|
||||
x, y = [1, 2]
|
||||
y
|
||||
end
|
||||
|
||||
def h y
|
||||
A::B.new.g y
|
||||
UnknownClass.some_method
|
||||
@f = 2
|
||||
@e
|
||||
@f
|
||||
@@b
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
C::D.new
|
||||
Reference in New Issue
Block a user