Files
codeql/ql/src/codeql_ruby/ast/internal/Module.qll
2021-05-20 14:27:13 +02:00

352 lines
11 KiB
Plaintext

private import codeql.Locations
private import codeql_ruby.AST
private import codeql_ruby.ast.Call
private import codeql_ruby.ast.Constant
private import codeql_ruby.ast.Expr
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"] }
cached
private module Cached {
cached
newtype TModule =
TResolved(string qName) {
qName = builtin()
or
qName = namespaceDeclaration(_)
} or
TUnresolved(Namespace n) { not exists(namespaceDeclaration(n)) }
cached
string namespaceDeclaration(Namespace n) {
isToplevel(n) and result = n.getName()
or
not isToplevel(n) and
not exists(n.getScopeExpr()) and
result = scopeAppend(namespaceDeclaration(n.getEnclosingModule()), n.getName())
or
exists(string container |
TResolved(container) = resolveScopeExpr(n.getScopeExpr()) and
result = scopeAppend(container, n.getName())
)
}
cached
Module getSuperClass(Module cls) {
cls = TResolved("Object") and result = TResolved("BasicObject")
or
cls = TResolved("Module") and result = TResolved("Object")
or
cls = TResolved("Class") and result = TResolved("Module")
or
not cls = TResolved(builtin()) and
(
exists(ClassDeclaration d |
d = cls.getADeclaration() and
result = resolveScopeExpr(d.getSuperclassExpr())
)
or
result = TResolved("Object") and
forex(ClassDeclaration d | d = cls.getADeclaration() |
not exists(resolveScopeExpr(d.getSuperclassExpr()))
)
)
}
cached
Module getAnIncludedModule(Module m) {
m = TResolved("Object") and result = TResolved("Kernel")
or
exists(IncludeOrPrependCall c |
c.getMethodName() = "include" and
(
m = resolveScopeExpr(c.getReceiver())
or
m = enclosingModule(c).getModule() and
c.getReceiver() instanceof Self
) and
result = resolveScopeExpr(c.getAnArgument())
)
}
cached
Module getAPrependedModule(Module m) {
exists(IncludeOrPrependCall c |
c.getMethodName() = "prepend" and
(
m = resolveScopeExpr(c.getReceiver())
or
m = enclosingModule(c).getModule() and
c.getReceiver() instanceof Self
) and
result = resolveScopeExpr(c.getAnArgument())
)
}
}
import Cached
private predicate isToplevel(ConstantAccess n) {
not exists(n.getScopeExpr()) and
(
n.hasGlobalScope()
or
n.getEnclosingModule() instanceof Toplevel
)
}
private predicate isDefinedConstant(string qualifiedModuleName) {
qualifiedModuleName = [builtin(), constantDefinition0(_)]
}
/**
* Resolve constant read access (typically a scope expression) 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.
*/
TResolved resolveScopeExpr(ConstantReadAccess r) {
exists(string qname |
qname =
min(string qn, int p |
isDefinedConstant(qn) and
qn = resolveScopeExpr(r, p) and
// prevent classes/modules that contain/extend themselves
not exists(ConstantWriteAccess w | qn = constantDefinition0(w) |
r = w.getScopeExpr()
or
r = w.(ClassDeclaration).getSuperclassExpr()
)
|
qn order by p
)
|
result = TResolved(qname)
or
exists(ConstantAssignment a |
qname = constantDefinition0(a) and
result = resolveScopeExpr(a.getParent().(Assignment).getRightOperand())
)
)
}
private int maxDepth() { result = 1 + max(int level | exists(enclosing(_, level))) }
private ModuleBase enclosing(ModuleBase m, int level) {
result = m and level = 0
or
result = enclosing(m.getEnclosingModule(), level - 1)
}
pragma[noinline]
private Namespace enclosingNameSpaceConstantReadAccess(
ConstantReadAccess c, int priority, string name
) {
result = enclosing(c.getEnclosingModule(), priority) and
name = c.getName()
}
/**
* Resolve constant read access (typically a scope expression) to a qualified name. The
* `priority` value indicates the precedence of the solution with respect to the lookup order.
* A constant name without scope specifier is resolved against its enclosing modules (inner-most first);
* if the constant is not found in any of the enclosing modules, then the constant will be resolved
* with respect to the ancestors (prepends, includes, super classes, and their ancestors) of the
* directly enclosing module.
*/
private string resolveScopeExpr(ConstantReadAccess c, int priority) {
c.hasGlobalScope() and result = c.getName() and priority = 0
or
exists(string name |
result = qualifiedModuleName(resolveScopeExprConstantReadAccess(c, priority, name), name)
)
or
not exists(c.getScopeExpr()) and
not c.hasGlobalScope() and
(
exists(string name |
exists(Namespace n |
n = enclosingNameSpaceConstantReadAccess(c, priority, name) and
result = qualifiedModuleName(constantDefinition0(n), name)
)
or
result =
qualifiedModuleName(ancestors(qualifiedModuleNameConstantReadAccess(c, name),
priority - maxDepth()), name)
)
or
priority = maxDepth() + 4 and
qualifiedModuleNameConstantReadAccess(c, result) != "BasicObject"
)
}
pragma[noinline]
private string resolveScopeExprConstantReadAccess(ConstantReadAccess c, int priority, string name) {
result = resolveScopeExpr(c.getScopeExpr(), priority) and
name = c.getName()
}
bindingset[qualifier, name]
private string scopeAppend(string qualifier, string name) {
if qualifier = "Object" then result = name else result = qualifier + "::" + name
}
private string qualifiedModuleName(ModuleBase m) {
result = "Object" and m instanceof Toplevel
or
result = constantDefinition0(m)
}
pragma[noinline]
private string qualifiedModuleNameConstantWriteAccess(ConstantWriteAccess c, string name) {
result = qualifiedModuleName(c.getEnclosingModule()) and
name = c.getName()
}
pragma[noinline]
private string qualifiedModuleNameConstantReadAccess(ConstantReadAccess c, string name) {
result = qualifiedModuleName(c.getEnclosingModule()) and
name = c.getName()
}
/**
* Get a qualified name for a constant definition. May return multiple qualified
* names because we over-approximate when resolving scope resolutions and ignore
* lookup order precedence. Taking lookup order into account here would lead to
* non-monotonic recursion.
*/
private string constantDefinition0(ConstantWriteAccess c) {
c.hasGlobalScope() and result = c.getName()
or
result = scopeAppend(resolveScopeExpr(c.getScopeExpr(), _), c.getName())
or
not exists(c.getScopeExpr()) and
not c.hasGlobalScope() and
exists(string name | result = scopeAppend(qualifiedModuleNameConstantWriteAccess(c, name), name))
}
/**
* The qualified names of the ancestors of a class/module. The ancestors should be an ordered list
* of the ancestores of `prepend`ed modules, the module itself , the ancestors or `include`d modules
* and the ancestors of the super class. The priority value only distinguishes the kind of ancestor,
* it does not order the ancestors within a group of the same kind. This is an over-approximation, however,
* computing the precise order is tricky because it depends on the evaluation/file loading order.
*/
// TODO: the order of super classes can be determined more precisely even without knowing the evaluation
// order, so we should be able to make this more precise.
private string ancestors(string qname, int priority) {
result = ancestors(prepends(qname), _) and priority = 0
or
result = qname and priority = 1 and isDefinedConstant(qname)
or
result = ancestors(includes(qname), _) and priority = 2
or
result = ancestors(superclass(qname), _) and priority = 3
}
private class IncludeOrPrependCall extends MethodCall {
IncludeOrPrependCall() { this.getMethodName() = ["include", "prepend"] }
string getAModule() { result = resolveScopeExpr(this.getAnArgument(), _) }
string getTarget() {
result = resolveScopeExpr(this.getReceiver(), _)
or
result = qualifiedModuleName(enclosingModule(this)) and
(
this.getReceiver() instanceof Self
or
not exists(this.getReceiver())
)
}
}
/**
* A variant of AstNode::getEnclosingModule that excludes
* results that are enclosed in a block. This is a bit wrong because
* it could lead to false negatives. However, `include` statements in
* blocks are very rare in normal code. The majority of cases are in calls
* to methods like `module_eval` and `Rspec.describe` / `Rspec.context`. These
* methods evaluate the block in the context of some other module/class instead of
* the enclosing one.
*/
private ModuleBase enclosingModule(AstNode node) { result = parent*(node).getParent() }
private AstNode parent(AstNode n) {
result = n.getParent() and
not result instanceof ModuleBase and
not result instanceof Block
}
private string prepends(string qname) {
exists(IncludeOrPrependCall m |
m.getMethodName() = "prepend" and
qname = m.getTarget() and
result = m.getAModule()
)
}
private string includes(string qname) {
qname = "Object" and
result = "Kernel"
or
exists(IncludeOrPrependCall m |
m.getMethodName() = "include" and
qname = m.getTarget() and
result = m.getAModule()
)
}
private Expr superexpr(string qname) {
exists(ClassDeclaration c | qname = constantDefinition0(c) and result = c.getSuperclassExpr())
}
private string superclass(string qname) {
qname = "Object" and result = "BasicObject"
or
result = resolveScopeExpr(superexpr(qname), _)
}
private string qualifiedModuleName(string container, string name) {
isDefinedConstant(result) and
(
container = result.regexpCapture("(.+)::([^:]+)", 1) and
name = result.regexpCapture("(.+)::([^:]+)", 2)
or
container = "Object" and name = result
)
}
private Module getAncestors(Module m) {
result = m or
result = getAncestors(m.getAnIncludedModule()) or
result = getAncestors(m.getAPrependedModule())
}
Method getMethod(TModule owner, string name) {
exists(ModuleBase m | m.getModule() = owner and result = m.getMethod(name))
}
private Method lookupMethod0(Module m, string name) {
result = lookupMethod0(m.getAPrependedModule(), name)
or
not exists(getMethod(getAncestors(m.getAPrependedModule()), name)) and
(
result = getMethod(m, name)
or
not exists(getMethod(m, name)) and result = lookupMethod0(m.getAnIncludedModule(), name)
)
}
Method lookupMethod(Module m, string name) {
result = lookupMethod0(m, name)
or
not exists(lookupMethod0(m, name)) and
result = lookupMethod(m.getSuperClass(), name)
}