From 201c1e4b8106b3198e0fc9292d317010a5d45d78 Mon Sep 17 00:00:00 2001 From: Arthur Baars Date: Tue, 30 Mar 2021 15:34:20 +0200 Subject: [PATCH] Basic module resolution --- ql/src/codeql_ruby/ast/Module.qll | 42 ++++++++ ql/src/codeql_ruby/ast/internal/Module.qll | 106 +++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 ql/src/codeql_ruby/ast/internal/Module.qll diff --git a/ql/src/codeql_ruby/ast/Module.qll b/ql/src/codeql_ruby/ast/Module.qll index 07a36091324..1126ba6253e 100644 --- a/ql/src/codeql_ruby/ast/Module.qll +++ b/ql/src/codeql_ruby/ast/Module.qll @@ -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) diff --git a/ql/src/codeql_ruby/ast/internal/Module.qll b/ql/src/codeql_ruby/ast/internal/Module.qll new file mode 100644 index 00000000000..d5e85d84258 --- /dev/null +++ b/ql/src/codeql_ruby/ast/internal/Module.qll @@ -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()) + ) + ) +}