Basic module resolution

This commit is contained in:
Arthur Baars
2021-03-30 15:34:20 +02:00
parent ea9afcd4e1
commit 201c1e4b81
2 changed files with 148 additions and 0 deletions

View File

@@ -1,8 +1,39 @@
private import codeql_ruby.AST
private import codeql_ruby.ast.Constant
private import internal.AST
private import internal.Module
private import internal.TreeSitter
/**
* A representation of a run-time `module` or `class` value.
*/
class Module extends TConstant {
Module() { this = TResolved(_, true) or this = TUnresolved(any(Namespace n)) }
string toString() {
this = TResolved(result, _)
or
exists(Namespace n | this = TUnresolved(n) and result = "...::" + n.toString())
}
Location getLocation() {
exists(Namespace n | this = TUnresolved(n) and result = n.getLocation())
or
result =
min(Namespace n, string qName, Location loc, int weight |
this = TResolved(qName, _) and
qName = constantDefinition(n) and
loc = n.getLocation() and
if exists(loc.getFile().getRelativePath()) then weight = 0 else weight = 1
|
loc
order by
weight, count(n.getAStmt()) desc, loc.getFile().getAbsolutePath(), loc.getStartLine(),
loc.getStartColumn()
)
}
}
/**
* The base class for classes, singleton classes, and modules.
*/
@@ -24,6 +55,9 @@ class ModuleBase extends BodyStmt, Scope, TModuleBase {
/** Gets the module named `name` in this module/class, if any. */
ModuleDefinition getModule(string name) { result = this.getAModule() and result.getName() = name }
/** Gets the representation of the run-time value of this module or class. */
Module getModule() { none() }
}
/**
@@ -62,6 +96,8 @@ class Toplevel extends ModuleBase, TToplevel {
pred = "getBeginBlock" and result = this.getBeginBlock(_)
}
final override Module getModule() { result = TResolved("Object", true) }
final override string toString() { result = g.getLocation().getFile().getBaseName() }
}
@@ -132,6 +168,12 @@ class Namespace extends ModuleBase, ConstantWriteAccess, TNamespace {
*/
override predicate hasGlobalScope() { none() }
final override Module getModule() {
result = any(string qName | qName = constantDefinition(this) | TResolved(qName, true))
or
result = TUnresolved(this)
}
override AstNode getAChild(string pred) {
result = ModuleBase.super.getAChild(pred) or
result = ConstantWriteAccess.super.getAChild(pred)

View File

@@ -0,0 +1,106 @@
private import codeql.Locations
private import codeql_ruby.ast.Constant
private import codeql_ruby.ast.Module
private import codeql_ruby.ast.Operation
private import codeql_ruby.ast.Scope
// Names of built-in modules and classes
private string builtin() { result = ["Object", "Kernel", "BasicObject", "Class", "Module"] }
newtype TConstant =
TResolved(string qName, boolean isModule) {
exists(ConstantWriteAccess n |
qName = builtin() and isModule = true
or
qName = constantDefinition(n) and
if n instanceof Namespace then isModule = true else isModule = false
)
} or
TUnresolved(ConstantWriteAccess n) { not exists(constantDefinition(n)) }
private predicate isToplevel(ConstantAccess n) {
not exists(n.getScopeExpr()) and
(
n.hasGlobalScope()
or
exists(Scope x | x.getADescendant() = n and x.getEnclosingModule() instanceof Toplevel)
)
}
private string constantDefinition0(ConstantWriteAccess n) { result = qualifiedNameForConstant0(n) }
/**
* Resolve a scope expression
*/
private string resolveScopeExpr0(ConstantReadAccess n) {
exists(string qname | qname = qualifiedNameForConstant0(n) |
qname = builtin() and result = qname
or
not qname = builtin() and
exists(ConstantWriteAccess def | qname = constantDefinition0(def) |
result = qname and def instanceof Namespace
or
result = resolveScopeExpr0(def.getParent().(Assignment).getRightOperand())
)
)
}
ModuleBase enclosing(ModuleBase m, int level) {
result = m and level = 0
or
result = enclosing(m.getOuterScope().getEnclosingModule(), level - 1)
}
private string resolveRelativeToEnclosing(ConstantAccess n, int i) {
not isToplevel(n) and
not exists(n.getScopeExpr()) and
exists(Scope s, ModuleBase enclosing |
n = s.getADescendant() and
enclosing = enclosing(s.getEnclosingModule(), i) and
(
result = constantDefinition0(enclosing) + "::" + n.getName()
or
enclosing instanceof Toplevel and result = n.getName()
)
)
}
private string qualifiedNameForConstant0(ConstantAccess n) {
isToplevel(n) and
result = n.getName()
or
result = resolveRelativeToEnclosing(n, 0)
or
result = resolveScopeExpr0(n.getScopeExpr()) + "::" + n.getName()
}
string constantDefinition(ConstantWriteAccess n) {
result = constantDefinition0(n)
or
result = resolveScopeExpr(n.getScopeExpr()) + "::" + n.getName()
}
private string resolveScopeExpr(ConstantReadAccess n) {
exists(string qname |
qname =
min(int i, string x |
(
x = qualifiedNameForConstant0(n) and i = 0
or
x = resolveRelativeToEnclosing(n, i)
) and
(x = builtin() or x = constantDefinition0(_))
|
x order by i
)
|
qname = builtin() and result = qname
or
not qname = builtin() and
exists(ConstantWriteAccess def | qname = constantDefinition0(def) |
result = qname and def instanceof Namespace
or
result = resolveScopeExpr(def.getParent().(Assignment).getRightOperand())
)
)
}