Ruby: make resolveConstant overlay[global]

This commit is contained in:
Nick Rolfe
2025-07-01 15:37:34 +01:00
parent b51940d1e2
commit e1f2433dbf
11 changed files with 69 additions and 5 deletions

View File

@@ -4,6 +4,7 @@ module;
private import codeql.ruby.AST
private import internal.AST
private import internal.Call
private import internal.Literal
private import internal.TreeSitter
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.dataflow.internal.DataFlowImplCommon
@@ -44,7 +45,7 @@ class Call extends Expr instanceof CallImpl {
final Expr getKeywordArgument(string keyword) {
exists(Pair p |
p = this.getAnArgument() and
p.getKey().getConstantValue().isSymbol(keyword) and
keyword = p.getKey().(SymbolLiteral).(StringlikeLiteralImpl).getStringValue() and
result = p.getValue()
)
}

View File

@@ -9,6 +9,7 @@ private import internal.Variable
private import internal.TreeSitter
/** A constant value. */
overlay[global]
class ConstantValue extends TConstantValue {
/** Gets a textual representation of this constant value. */
final string toString() { this.hasValueWithType(result, _) }
@@ -137,6 +138,7 @@ class ConstantValue extends TConstantValue {
}
/** Provides different sub classes of `ConstantValue`. */
overlay[global]
module ConstantValue {
/** A constant integer value. */
class ConstantIntegerValue extends ConstantValue, TInt { }
@@ -271,15 +273,18 @@ class ConstantReadAccess extends ConstantAccess {
*
* the value being read at `M::CONST` is `"const"`.
*/
overlay[global]
Expr getValue() { result = getConstantReadAccessValue(this) }
/**
* Gets a fully qualified name for this constant read, based on the context in
* which it occurs.
*/
overlay[global]
string getAQualifiedName() { result = resolveConstant(this) }
/** Gets the module that this read access resolves to, if any. */
overlay[global]
Module getModule() { result = resolveConstantReadAccess(this) }
final override string getAPrimaryQlClass() { result = "ConstantReadAccess" }
@@ -345,6 +350,7 @@ class ConstantWriteAccess extends ConstantAccess {
* constants up the namespace chain, the fully qualified name of a nested
* constant can be ambiguous from just statically looking at the AST.
*/
overlay[global]
string getAQualifiedName() { result = resolveConstantWrite(this) }
}

View File

@@ -15,6 +15,7 @@ private import internal.TreeSitter
*/
class Expr extends Stmt, TExpr {
/** Gets the constant value of this expression, if any. */
overlay[global]
ConstantValue getConstantValue() { result = getConstantValueExpr(this) }
}
@@ -428,6 +429,7 @@ class StringConcatenation extends Expr, TStringConcatenation {
* "foo" "bar#{ n }"
* ```
*/
overlay[global]
final string getConcatenatedValueText() {
forall(StringLiteral c | c = this.getString(_) |
exists(c.getConstantValue().getStringlikeValue())

View File

@@ -44,6 +44,7 @@ class IntegerLiteral extends NumericLiteral instanceof IntegerLiteralImpl {
/** Gets the numerical value of this integer literal. */
final int getValue() { result = super.getValue() }
overlay[global]
final override ConstantValue::ConstantIntegerValue getConstantValue() {
result = NumericLiteral.super.getConstantValue()
}
@@ -60,6 +61,7 @@ class IntegerLiteral extends NumericLiteral instanceof IntegerLiteralImpl {
* ```
*/
class FloatLiteral extends NumericLiteral instanceof FloatLiteralImpl {
overlay[global]
final override ConstantValue::ConstantFloatValue getConstantValue() {
result = NumericLiteral.super.getConstantValue()
}
@@ -75,6 +77,7 @@ class FloatLiteral extends NumericLiteral instanceof FloatLiteralImpl {
* ```
*/
class RationalLiteral extends NumericLiteral instanceof RationalLiteralImpl {
overlay[global]
final override ConstantValue::ConstantRationalValue getConstantValue() {
result = NumericLiteral.super.getConstantValue()
}
@@ -90,6 +93,7 @@ class RationalLiteral extends NumericLiteral instanceof RationalLiteralImpl {
* ```
*/
class ComplexLiteral extends NumericLiteral instanceof ComplexLiteralImpl {
overlay[global]
final override ConstantValue::ConstantComplexValue getConstantValue() {
result = NumericLiteral.super.getConstantValue()
}
@@ -99,6 +103,7 @@ class ComplexLiteral extends NumericLiteral instanceof ComplexLiteralImpl {
/** A `nil` literal. */
class NilLiteral extends Literal instanceof NilLiteralImpl {
overlay[global]
final override ConstantValue::ConstantNilValue getConstantValue() { result = TNil() }
final override string getAPrimaryQlClass() { result = "NilLiteral" }
@@ -125,6 +130,7 @@ class BooleanLiteral extends Literal instanceof BooleanLiteralImpl {
/** Gets the value of this Boolean literal. */
boolean getValue() { result = super.getValue() }
overlay[global]
final override ConstantValue::ConstantBooleanValue getConstantValue() {
result = Literal.super.getConstantValue()
}
@@ -136,6 +142,7 @@ class BooleanLiteral extends Literal instanceof BooleanLiteralImpl {
class EncodingLiteral extends Literal instanceof EncodingLiteralImpl {
final override string getAPrimaryQlClass() { result = "EncodingLiteral" }
overlay[global]
final override ConstantValue::ConstantStringValue getConstantValue() {
result = Literal.super.getConstantValue()
}
@@ -147,6 +154,7 @@ class EncodingLiteral extends Literal instanceof EncodingLiteralImpl {
class LineLiteral extends Literal instanceof LineLiteralImpl {
final override string getAPrimaryQlClass() { result = "LineLiteral" }
overlay[global]
final override ConstantValue::ConstantIntegerValue getConstantValue() {
result = Literal.super.getConstantValue()
}
@@ -158,6 +166,7 @@ class LineLiteral extends Literal instanceof LineLiteralImpl {
class FileLiteral extends Literal instanceof FileLiteralImpl {
final override string getAPrimaryQlClass() { result = "FileLiteral" }
overlay[global]
final override ConstantValue::ConstantStringValue getConstantValue() {
result = Literal.super.getConstantValue()
}
@@ -169,6 +178,7 @@ class FileLiteral extends Literal instanceof FileLiteralImpl {
*/
class StringComponent extends AstNode instanceof StringComponentImpl {
/** Gets the constant value of this string component, if any. */
overlay[global]
ConstantValue::ConstantStringValue getConstantValue() { result = TString(super.getValue()) }
}
@@ -213,6 +223,7 @@ class StringInterpolationComponent extends StringComponent, StmtSequence instanc
final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
overlay[global]
final override ConstantValue::ConstantStringValue getConstantValue() {
result = StmtSequence.super.getConstantValue()
}
@@ -260,6 +271,7 @@ class RegExpInterpolationComponent extends RegExpComponent, StmtSequence instanc
final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
overlay[global]
final override ConstantValue::ConstantStringValue getConstantValue() {
result = StmtSequence.super.getConstantValue()
}
@@ -408,6 +420,7 @@ class SymbolLiteral extends StringlikeLiteral instanceof SymbolLiteralImpl {
not this instanceof MethodName and result = "SymbolLiteral"
}
overlay[global]
final override ConstantValue::ConstantSymbolValue getConstantValue() {
result = StringlikeLiteral.super.getConstantValue()
}
@@ -440,6 +453,7 @@ class SubshellLiteral extends StringlikeLiteral instanceof SubshellLiteralImpl {
class CharacterLiteral extends Literal instanceof CharacterLiteralImpl {
final override string getAPrimaryQlClass() { result = "CharacterLiteral" }
overlay[global]
final override ConstantValue::ConstantStringValue getConstantValue() {
result = Literal.super.getConstantValue()
}

View File

@@ -43,18 +43,22 @@ class MethodBase extends Callable, BodyStmt, Scope, TMethodBase {
* Holds if this method is public.
* Methods are public by default.
*/
overlay[global]
predicate isPublic() { this.getVisibility() = "public" }
/** Holds if this method is private. */
overlay[global]
predicate isPrivate() { this.getVisibility() = "private" }
/** Holds if this method is protected. */
overlay[global]
predicate isProtected() { this.getVisibility() = "protected" }
/**
* Gets a string describing the visibility of this method.
* This is either 'public', 'private' or 'protected'.
*/
overlay[global]
string getVisibility() {
result = getVisibilityModifier(this).getVisibility()
or
@@ -76,6 +80,7 @@ class MethodBase extends Callable, BodyStmt, Scope, TMethodBase {
* end
* ```
*/
overlay[global]
private VisibilityModifier getExplicitVisibilityModifier(Method m) {
result.getMethodArgument() = m
or
@@ -89,6 +94,7 @@ private VisibilityModifier getExplicitVisibilityModifier(Method m) {
* Gets the visibility modifier that defines the visibility of method `m`, if
* any.
*/
overlay[global]
private VisibilityModifier getVisibilityModifier(MethodBase mb) {
mb =
any(Method m |
@@ -205,6 +211,7 @@ class Method extends MethodBase, TMethod {
* end
* ```
*/
overlay[global]
override predicate isPrivate() { super.isPrivate() }
final override Parameter getParameter(int n) {
@@ -213,6 +220,7 @@ class Method extends MethodBase, TMethod {
final override string toString() { result = this.getName() }
overlay[global]
override string getVisibility() {
result = getVisibilityModifier(this).getVisibility()
or
@@ -226,6 +234,7 @@ class Method extends MethodBase, TMethod {
}
}
overlay[global]
pragma[nomagic]
private predicate modifiesIn(VisibilityModifier vm, ModuleBase n, string name) {
n = vm.getEnclosingModule() and
@@ -302,6 +311,7 @@ class SingletonMethod extends MethodBase, TSingletonMethod {
* end
* ```
*/
overlay[global]
override predicate isPrivate() { super.isPrivate() }
}

View File

@@ -11,6 +11,7 @@ private import internal.Scope
/**
* A representation of a run-time `module` or `class` value.
*/
overlay[global]
class Module extends TModule {
/** Gets a declaration of this module, if any. */
ModuleBase getADeclaration() { result.getModule() = this }
@@ -258,6 +259,7 @@ class ModuleBase extends BodyStmt, Scope, TModuleBase {
}
/** Gets the representation of the run-time value of this module or class. */
overlay[global]
Module getModule() { none() }
/**
@@ -336,6 +338,7 @@ class Toplevel extends ModuleBase, TToplevel {
pred = "getBeginBlock" and result = this.getBeginBlock(_)
}
overlay[global]
final override Module getModule() { result = TResolved("Object") }
final override string toString() { result = g.getLocation().getFile().getBaseName() }
@@ -408,6 +411,7 @@ class Namespace extends ModuleBase, ConstantWriteAccess, TNamespace {
*/
override predicate hasGlobalScope() { none() }
overlay[global]
final override Module getModule() {
result = any(string qName | qName = namespaceDeclaration(this) | TResolved(qName))
or

View File

@@ -206,6 +206,7 @@ class HashPattern extends CasePattern, THashPattern {
}
/** Gets the value for a given key name. */
overlay[global]
CasePattern getValueByKey(string key) {
exists(int i |
this.getKey(i).getConstantValue().isStringlikeValue(key) and result = this.getValue(i)

View File

@@ -1,6 +1,3 @@
overlay[local]
module;
private import codeql.ruby.AST
private import codeql.ruby.ast.internal.AST
private import codeql.ruby.ast.internal.Literal
@@ -41,6 +38,7 @@ private import ExprNodes
* constant value in some cases.
*/
private module Propagation {
overlay[local]
ExprCfgNode getSource(VariableReadAccessCfgNode read) {
exists(Ssa::WriteDefinition def |
def.assigns(result) and
@@ -202,6 +200,7 @@ private module Propagation {
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isComplex(n, real, imaginary))
}
overlay[local]
private class StringlikeLiteralWithInterpolationCfgNode extends StringlikeLiteralCfgNode {
StringlikeLiteralWithInterpolationCfgNode() {
this.getAComponent() =
@@ -211,6 +210,7 @@ private module Propagation {
)
}
overlay[global]
pragma[nomagic]
private string getComponentValue(int i) {
this.getComponent(i) =
@@ -222,17 +222,20 @@ private module Propagation {
}
language[monotonicAggregates]
overlay[global]
private string getValue() {
result =
strictconcat(int i | exists(this.getComponent(i)) | this.getComponentValue(i) order by i)
}
overlay[global]
pragma[nomagic]
string getSymbolValue() {
result = this.getValue() and
this.getExpr() instanceof SymbolLiteral
}
overlay[global]
pragma[nomagic]
string getStringValue() {
result = this.getValue() and
@@ -240,6 +243,7 @@ private module Propagation {
not this.getExpr() instanceof RegExpLiteral
}
overlay[global]
pragma[nomagic]
string getRegExpValue(string flags) {
result = this.getValue() and
@@ -569,6 +573,7 @@ private predicate isArrayExpr(Expr e, ArrayLiteralCfgNode arr) {
isArrayExpr(e.(MethodCall).getReceiver(), arr)
}
overlay[local]
private class TokenConstantAccess extends ConstantAccess, TTokenConstantAccess {
private Ruby::Constant g;
@@ -580,6 +585,7 @@ private class TokenConstantAccess extends ConstantAccess, TTokenConstantAccess {
/**
* A constant access that has a scope resolution qualifier.
*/
overlay[local]
class ScopeResolutionConstantAccess extends ConstantAccess, TScopeResolutionConstantAccess {
private Ruby::ScopeResolution g;
private Ruby::Constant constant;
@@ -598,6 +604,7 @@ class ScopeResolutionConstantAccess extends ConstantAccess, TScopeResolutionCons
final override predicate hasGlobalScope() { not exists(g.getScope()) }
}
overlay[local]
private class ConstantReadAccessSynth extends ConstantAccess, TConstantReadAccessSynth {
private string value;
@@ -612,6 +619,7 @@ private class ConstantReadAccessSynth extends ConstantAccess, TConstantReadAcces
final override predicate hasGlobalScope() { value.matches("::%") }
}
overlay[local]
private class ConstantWriteAccessSynth extends ConstantAccess, TConstantWriteAccessSynth {
private string value;

View File

@@ -14,6 +14,7 @@ private string builtin() {
]
}
overlay[global]
cached
private module Cached {
cached
@@ -218,6 +219,7 @@ private string scopeAppend(string qualifier, string name) {
* both as a performance optimization (minimize non-linear recursion), and as a way
* to prevent infinite recursion.
*/
overlay[global]
private module ResolveImpl {
private ModuleBase enclosing(ModuleBase m, int level) {
result = m and level = 0
@@ -586,6 +588,7 @@ private ModuleBase enclosingModuleNoBlock(Stmt node) {
result = enclosingScopesNoBlock(Scope::scopeOfInclSynth(node))
}
overlay[global]
private Module getAncestors(Module m) {
result = m or
result = getAncestors(m.getAnIncludedModule()) or
@@ -596,6 +599,7 @@ private newtype TMethodOrExpr =
TMethod(Method m) or
TExpr(Expr e)
overlay[global]
private TMethodOrExpr getMethodOrConst(TModule owner, string name) {
exists(ModuleBase m | m.getModule() = owner |
result = TMethod(m.getMethod(name))
@@ -604,12 +608,14 @@ private TMethodOrExpr getMethodOrConst(TModule owner, string name) {
)
}
overlay[global]
module ExposedForTestingOnly {
Method getMethod(TModule owner, string name) { TMethod(result) = getMethodOrConst(owner, name) }
Expr getConst(TModule owner, string name) { TExpr(result) = getMethodOrConst(owner, name) }
}
overlay[global]
private TMethodOrExpr lookupMethodOrConst0(Module m, string name) {
result = lookupMethodOrConst0(m.getAPrependedModule(), name)
or
@@ -624,6 +630,7 @@ private TMethodOrExpr lookupMethodOrConst0(Module m, string name) {
private AstNode getNode(TMethodOrExpr e) { e = TMethod(result) or e = TExpr(result) }
overlay[global]
private TMethodOrExpr lookupMethodOrConst(Module m, string name) {
result = lookupMethodOrConst0(m, name)
or

View File

@@ -6,7 +6,6 @@ private import codeql.ruby.AST
private import codeql.ruby.controlflow.BasicBlocks
private import codeql.ruby.dataflow.SSA
private import codeql.ruby.ast.internal.Constant
private import codeql.ruby.ast.internal.Literal
private import ControlFlowGraph
private import internal.ControlFlowGraphImpl as CfgImpl
@@ -38,11 +37,13 @@ class ExitNode extends CfgNode, CfgImpl::ExitNode {
*/
class AstCfgNode extends CfgNode, CfgImpl::AstCfgNode {
/** Gets the name of the primary QL class for this node. */
overlay[global]
override string getAPrimaryQlClass() { result = "AstCfgNode" }
}
/** A control-flow node that wraps an AST expression. */
class ExprCfgNode extends AstCfgNode {
overlay[global]
override string getAPrimaryQlClass() { result = "ExprCfgNode" }
Expr e;
@@ -53,6 +54,7 @@ class ExprCfgNode extends AstCfgNode {
Expr getExpr() { result = e }
/** Gets the constant value of this expression, if any. */
overlay[global]
ConstantValue getConstantValue() { result = getConstantValue(this) }
}
@@ -78,6 +80,7 @@ class StringComponentCfgNode extends AstCfgNode {
StringComponentCfgNode() { this.getAstNode() instanceof StringComponent }
/** Gets the constant value of this string component. */
overlay[global]
ConstantValue getConstantValue() {
result = this.getAstNode().(StringComponent).getConstantValue()
}
@@ -299,6 +302,7 @@ module ExprNodes {
/** A control-flow node that wraps a `Call` AST expression. */
class CallCfgNode extends ExprCfgNode {
overlay[global]
override string getAPrimaryQlClass() { result = "CallCfgNode" }
override CallExprChildMapping e;
@@ -312,6 +316,7 @@ module ExprNodes {
final ExprCfgNode getAnArgument() { result = this.getArgument(_) }
/** Gets the keyword argument whose key is `keyword` of this call. */
overlay[global]
final ExprCfgNode getKeywordArgument(string keyword) {
exists(PairCfgNode n |
e.hasCfgChild(e.getAnArgument(), this, n) and
@@ -340,6 +345,7 @@ module ExprNodes {
/** A control-flow node that wraps a `MethodCall` AST expression. */
class MethodCallCfgNode extends CallCfgNode {
overlay[global]
override string getAPrimaryQlClass() { result = "MethodCallCfgNode" }
MethodCallCfgNode() { super.getExpr() instanceof MethodCall }
@@ -844,6 +850,7 @@ module ExprNodes {
this.getAstNode() instanceof StringInterpolationComponent
}
overlay[global]
final override ConstantValue getConstantValue() {
result = StmtSequenceCfgNode.super.getConstantValue()
}
@@ -857,6 +864,7 @@ module ExprNodes {
this.getAstNode() instanceof RegExpInterpolationComponent
}
overlay[global]
final override ConstantValue getConstantValue() {
result = StmtSequenceCfgNode.super.getConstantValue()
}
@@ -948,6 +956,7 @@ module ExprNodes {
* into calls to `Array.[]`, so this includes both desugared calls as well as
* explicit calls.
*/
overlay[global]
class ArrayLiteralCfgNode extends MethodCallCfgNode {
override string getAPrimaryQlClass() { result = "ArrayLiteralCfgNode" }
@@ -965,6 +974,7 @@ module ExprNodes {
* into calls to `Hash.[]`, so this includes both desugared calls as well as
* explicit calls.
*/
overlay[global]
class HashLiteralCfgNode extends MethodCallCfgNode {
override string getAPrimaryQlClass() { result = "HashLiteralCfgNode" }

View File

@@ -37,6 +37,7 @@ class CfgScope extends Scope instanceof CfgImpl::CfgScopeImpl {
*/
class CfgNode extends CfgImpl::Node {
/** Gets the name of the primary QL class for this node. */
overlay[global]
string getAPrimaryQlClass() { none() }
/** Gets the file of this control flow node. */