mirror of
https://github.com/github/codeql.git
synced 2025-12-25 05:06:34 +01:00
352 lines
11 KiB
Plaintext
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)
|
|
}
|