зеркало из https://github.com/github/codeql.git
Ruby: add ConstRef
This commit is contained in:
Родитель
06ec03de74
Коммит
b632e21ba0
|
@ -1084,3 +1084,233 @@ class ArrayLiteralNode extends LocalSourceNode, ExprNode {
|
|||
*/
|
||||
Node getAnElement() { result = this.(CallNode).getPositionalArgument(_) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A place in which a named constant can be found up during constant lookup.
|
||||
*/
|
||||
private newtype TConstLookupScope =
|
||||
/** Look up in a qualified constant name `base::`. */
|
||||
MkQualifiedLookup(ConstantAccess base) or
|
||||
/** Look up in the ancestors of `mod`. */
|
||||
MkAncestorLookup(Module mod) or
|
||||
/** Look up in a module syntactically nested in `scope`. */
|
||||
MkNestedLookup(ModuleBase scope) or
|
||||
/** Pseudo-scope for accesses that are known to resolve to `mod`. */
|
||||
MkExactLookup(Module mod)
|
||||
|
||||
/**
|
||||
* Gets a `LocalSourceNode` to represent the constant read or written by `access`.
|
||||
*/
|
||||
private LocalSourceNode getConstantAccessNode(ConstantAccess access) {
|
||||
// Namespaces don't evaluate to the constant being accessed, they return the value of their last statement.
|
||||
// Use the definition of 'self' in the namespace as the representative in this case.
|
||||
if access instanceof Namespace
|
||||
then
|
||||
result.(SsaDefinitionNode).getDefinition().(Ssa::SelfDefinition).getSourceVariable() =
|
||||
access.(Namespace).getModuleSelfVariable()
|
||||
else result.asExpr().getExpr() = access
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a constant, such as `C`, `C::D`, or a class or module declaration.
|
||||
*
|
||||
* See `DataFlow::getConst` for usage example.
|
||||
*/
|
||||
class ConstRef extends LocalSourceNode {
|
||||
private ConstantAccess access;
|
||||
|
||||
ConstRef() { this = getConstantAccessNode(access) }
|
||||
|
||||
/** Gets the underlying constant access AST node. */
|
||||
ConstantAccess asConstantAccess() { result = access }
|
||||
|
||||
/** Gets the underlying module declaration, if any. */
|
||||
Namespace asNamespaceDeclaration() { result = access }
|
||||
|
||||
/** Gets the module defined or re-opened by this constant access, if any. */
|
||||
ModuleNode asModule() { result.getADeclaration() = access }
|
||||
|
||||
/**
|
||||
* Gets the simple name of the constant being referenced, such as
|
||||
* the `B` in `A::B`.
|
||||
*/
|
||||
string getName() { result = access.getName() }
|
||||
|
||||
/**
|
||||
* Holds if this might refer to a top-level constant.
|
||||
*/
|
||||
predicate isPossiblyGlobal() {
|
||||
exists(Module mod |
|
||||
not exists(mod.getCanonicalEnclosingModule()) and
|
||||
mod.getAnImmediateReference() = access
|
||||
)
|
||||
or
|
||||
not exists(Module mod | mod.getAnImmediateReference() = access) and
|
||||
not exists(access.getScopeExpr())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a module for which this constant is the reference to an ancestor module.
|
||||
*
|
||||
* For example, `M` is the ancestry target of `C` in the following examples:
|
||||
* ```rb
|
||||
* class M < C {}
|
||||
*
|
||||
* module M
|
||||
* include C
|
||||
* end
|
||||
*
|
||||
* module M
|
||||
* prepend C
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private ModuleNode getAncestryTarget() { result.getAnAncestorExpr() = this }
|
||||
|
||||
/**
|
||||
* Gets a module scope in which the value of this constant is part of `Module.nesting`.
|
||||
*/
|
||||
private ModuleBase getANestingScope() {
|
||||
result = this.getAncestryTarget().getADeclaration()
|
||||
or
|
||||
result.getEnclosingModule() = this.getANestingScope()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the known target module.
|
||||
*
|
||||
* We resolve these differently to prune out infeasible constant lookups.
|
||||
*/
|
||||
private Module getExactTarget() { result.getAnImmediateReference() = access }
|
||||
|
||||
/**
|
||||
* Gets a scope in which a constant lookup may access the contents of the module referenced by this constant.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private TConstLookupScope getATargetScope() {
|
||||
result = MkAncestorLookup(this.getAncestryTarget().getADirectDescendent*())
|
||||
or
|
||||
access = any(ConstantAccess ac).getScopeExpr() and
|
||||
result = MkQualifiedLookup(access)
|
||||
or
|
||||
result = MkNestedLookup(this.getANestingScope())
|
||||
or
|
||||
result = MkExactLookup(access.(Namespace).getModule())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scope expression, or the immediately enclosing `Namespace` (skipping over singleton classes).
|
||||
*
|
||||
* Top-levels are not included, since this is only needed for nested constant lookup, and unqualified constants
|
||||
* at the top-level are handled by `DataFlow::getConst`, never `ConstRef.getConst`.
|
||||
*/
|
||||
private TConstLookupScope getLookupScope() {
|
||||
result = MkQualifiedLookup(access.getScopeExpr())
|
||||
or
|
||||
not exists(this.getExactTarget()) and
|
||||
not exists(access.getScopeExpr()) and
|
||||
not access.hasGlobalScope() and
|
||||
(
|
||||
result = MkAncestorLookup(access.getEnclosingModule().getNamespaceOrToplevel().getModule())
|
||||
or
|
||||
result = MkNestedLookup(access.getEnclosingModule())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this can reference a constant named `name` from `scope` using a lookup of `kind`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate accesses(TConstLookupScope scope, string name) {
|
||||
scope = this.getLookupScope() and
|
||||
name = this.getName()
|
||||
or
|
||||
exists(Module mod |
|
||||
this.getExactTarget() = mod.getCanonicalNestedModule(name) and
|
||||
scope = MkExactLookup(mod)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a constant reference that may resolve to a member of this node.
|
||||
*
|
||||
* For example `DataFlow::getConst("A").getConst("B")` finds the following:
|
||||
* ```rb
|
||||
* A::B # simple reference
|
||||
*
|
||||
* module A
|
||||
* B # in scope
|
||||
* module X
|
||||
* B # in nested scope
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* module X
|
||||
* include A
|
||||
* B # via inclusion
|
||||
* end
|
||||
*
|
||||
* class X < A
|
||||
* B # via subclassing
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
ConstRef getConst(string name) {
|
||||
exists(TConstLookupScope scope |
|
||||
pragma[only_bind_into](scope) = pragma[only_bind_out](this).getATargetScope() and
|
||||
result.accesses(pragma[only_bind_out](scope), name)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a module that transitively subclasses, includes, or prepends the module referred to by
|
||||
* this constant.
|
||||
*
|
||||
* For example, `DataFlow::getConst("A").getADescendentModule()` finds `B`, `C`, and `E`:
|
||||
* ```rb
|
||||
* class B < A
|
||||
* end
|
||||
*
|
||||
* class C < B
|
||||
* end
|
||||
*
|
||||
* module E
|
||||
* include C
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
ModuleNode getADescendentModule() { MkAncestorLookup(result) = this.getATargetScope() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a constant reference that may resolve to the top-level constant `name`.
|
||||
*
|
||||
* To get nested constants, call `getConst()` one or more times on the result.
|
||||
*
|
||||
* For example `DataFlow::getConst("A").getConst("B")` finds the following:
|
||||
* ```rb
|
||||
* A::B # simple reference
|
||||
*
|
||||
* module A
|
||||
* B # in scope
|
||||
* module X
|
||||
* B # in nested scope
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* module X
|
||||
* include A
|
||||
* B # via inclusion
|
||||
* end
|
||||
*
|
||||
* class X < A
|
||||
* B # via subclassing
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
pragma[nomagic]
|
||||
ConstRef getConst(string name) {
|
||||
result.getName() = name and
|
||||
result.isPossiblyGlobal()
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче