Files
codeql/ruby/ql/lib/codeql/ruby/ast/Constant.qll
2025-07-09 11:59:25 +01:00

368 lines
11 KiB
Plaintext

overlay[local]
module;
private import codeql.ruby.AST
private import internal.AST
private import internal.Constant
private import internal.Module
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, _) }
/** Gets a string describing the type of this constant value. */
string getValueType() { this.hasValueWithType(_, result) }
private predicate hasValueWithType(string value, string type) {
value = this.getInt().toString() and type = "int"
or
value = this.getFloat().toString() and type = "float"
or
exists(int numerator, int denominator |
this.isRational(numerator, denominator) and
value = numerator + "/" + denominator and
type = "rational"
)
or
exists(float real, float imaginary |
this.isComplex(real, imaginary) and
value = real + "+" + imaginary + "i" and
type = "complex"
)
or
value = this.getString() and type = "string"
or
value = ":" + this.getSymbol() and type = "symbol"
or
value = this.getRegExp() and type = "regexp"
or
value = this.getBoolean().toString() and type = "boolean"
or
this.isNil() and value = "nil" and type = "nil"
}
/** Gets the integer value, if this is an integer. */
int getInt() { this = TInt(result) }
/** Holds if this is the integer value `i`. */
predicate isInt(int i) { i = this.getInt() }
/** Gets the float value, if this is a float. */
float getFloat() { this = TFloat(result) }
/** Holds if this is the float value `f`. */
predicate isFloat(float f) { f = this.getFloat() }
/** Holds if this is the rational value `numerator / denominator`. */
predicate isRational(int numerator, int denominator) { this = TRational(numerator, denominator) }
/** Holds if this is the complex value `real + imaginary * i`. */
predicate isComplex(float real, float imaginary) { this = TComplex(real, imaginary) }
/** Gets the string value, if this is a string. */
string getString() { this = TString(result) }
/** Holds if this is the string value `s`. */
predicate isString(string s) { s = this.getString() }
/** Gets the symbol value (excluding the `:` prefix), if this is a symbol. */
string getSymbol() { this = TSymbol(result) }
/** Holds if this is the symbol value `:s`. */
predicate isSymbol(string s) { s = this.getSymbol() }
/** Gets the regexp value, if this is a regexp. */
string getRegExp() { this.isRegExpWithFlags(result, _) }
/** Holds if this is the regexp value `/s/`, ignoring any flags. */
predicate isRegExp(string s) { this.isRegExpWithFlags(s, _) }
/** Holds if this is the regexp value `/s/flags` . */
predicate isRegExpWithFlags(string s, string flags) { this = TRegExp(s, flags) }
/** Gets the string/symbol/regexp value, if any. */
string getStringlikeValue() { result = [this.getString(), this.getSymbol(), this.getRegExp()] }
/**
* Holds if this is:
* - the string value `s`,
* - the symbol value `:s`, or
* - the regexp value `/s/`.
*/
predicate isStringlikeValue(string s) { s = this.getStringlikeValue() }
/** Gets the Boolean value, if this is a Boolean. */
boolean getBoolean() { this = TBoolean(result) }
/** Holds if this is the Boolean value `b`. */
predicate isBoolean(boolean b) { b = this.getBoolean() }
/** Holds if this is the `nil` value. */
predicate isNil() { this = TNil() }
/** Gets a (unique) serialized version of this value. */
final string serialize() {
result = this.getInt().toString()
or
exists(string res | res = this.getFloat().toString() |
if exists(res.indexOf(".")) then result = res else result = res + ".0"
)
or
exists(int numerator, int denominator |
this.isRational(numerator, denominator) and
result = numerator + "/" + denominator
)
or
exists(float real, float imaginary |
this.isComplex(real, imaginary) and
result = real + "+" + imaginary + "i"
)
or
result = "\"" + this.getString().replaceAll("\"", "\\\"") + "\""
or
result = ":" + this.getSymbol()
or
exists(string s, string flags |
this.isRegExpWithFlags(s, flags) and
result = "/" + s + "/" + flags
)
or
result = this.getBoolean().toString()
or
this.isNil() and result = "nil"
}
}
/** Provides different sub classes of `ConstantValue`. */
overlay[global]
module ConstantValue {
/** A constant integer value. */
class ConstantIntegerValue extends ConstantValue, TInt { }
/** A constant float value. */
class ConstantFloatValue extends ConstantValue, TFloat { }
/** A constant rational value. */
class ConstantRationalValue extends ConstantValue, TRational { }
/** A constant complex value. */
class ConstantComplexValue extends ConstantValue, TComplex { }
/** A constant string-like value. */
class ConstantStringlikeValue extends ConstantValue, TStringlike { }
/** A constant string value. */
class ConstantStringValue extends ConstantStringlikeValue, TString { }
/** A constant symbol value. */
class ConstantSymbolValue extends ConstantStringlikeValue, TSymbol { }
/** A constant regexp value. */
class ConstantRegExpValue extends ConstantStringlikeValue, TRegExp { }
/** A constant Boolean value. */
class ConstantBooleanValue extends ConstantValue, TBoolean { }
/** A constant `nil` value. */
class ConstantNilValue extends ConstantValue, TNil { }
/** Gets the integer constant `x`. */
ConstantValue fromInt(int x) { result.getInt() = x }
/** Gets the float constant `x`. */
ConstantValue fromFloat(float x) { result.getFloat() = x }
/** Gets the string constant `x`. */
ConstantValue fromString(string x) { result.getString() = x }
/** Gets the symbol constant `x`. */
ConstantValue fromSymbol(string x) { result.getSymbol() = x }
/** Gets the regexp constant `x`. */
ConstantValue fromRegExp(string x) { result.getRegExp() = x }
/** Gets the string, symbol, or regexp constant `x`. */
ConstantValue fromStringlikeValue(string x) { result.getStringlikeValue() = x }
}
/** An access to a constant. */
class ConstantAccess extends Expr, TConstantAccess {
/** Gets the name of the constant being accessed. */
string getName() { none() }
/** Holds if the name of the constant being accessed is `name`. */
final predicate hasName(string name) { this.getName() = name }
/**
* Gets the expression used in the access's scope resolution operation, if
* any. In the following example, the result is the `Call` expression for
* `foo()`.
*
* ```rb
* foo()::MESSAGE
* ```
*
* However, there is no result for the following example, since there is no
* scope resolution operation.
*
* ```rb
* MESSAGE
* ```
*/
Expr getScopeExpr() { none() }
/**
* Holds if the access uses the scope resolution operator to refer to the
* global scope, as in this example:
*
* ```rb
* ::MESSAGE
* ```
*/
predicate hasGlobalScope() { none() }
override string toString() { result = this.getName() }
override AstNode getAChild(string pred) {
result = super.getAChild(pred)
or
pred = "getScopeExpr" and result = this.getScopeExpr()
}
}
/**
* A use (read) of a constant.
*
* For example, the right-hand side of the assignment in:
*
* ```rb
* x = Foo
* ```
*
* Or the superclass `Bar` in this example:
*
* ```rb
* class Foo < Bar
* end
* ```
*/
class ConstantReadAccess extends ConstantAccess {
ConstantReadAccess() {
not this instanceof ConstantWriteAccess
or
// `X` in `X ||= 10` is considered both a read and a write
this = any(AssignOperation a).getLeftOperand()
or
this instanceof TConstantReadAccessSynth
}
/**
* Gets the value being read, if any. For example, in
*
* ```rb
* module M
* CONST = "const"
* end
*
* puts M::CONST
* ```
*
* 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" }
}
/**
* A definition of a constant.
*
* Examples:
*
* ```rb
* Foo = 1 # defines constant Foo as an integer
* M::Foo = 1 # defines constant Foo as an integer in module M
*
* class Bar; end # defines constant Bar as a class
* class M::Bar; end # defines constant Bar as a class in module M
*
* module Baz; end # defines constant Baz as a module
* module M::Baz; end # defines constant Baz as a module in module M
* ```
*/
class ConstantWriteAccess extends ConstantAccess {
ConstantWriteAccess() {
explicitAssignmentNode(toGenerated(this), _) or
this instanceof TNamespace or
this instanceof TConstantWriteAccessSynth
}
override string getAPrimaryQlClass() { result = "ConstantWriteAccess" }
/**
* Gets a fully qualified name for this constant, based on the context in
* which it is defined.
*
* For example, given
* ```rb
* module Foo
* module Bar
* class Baz
* end
* end
* CONST_A = "a"
* end
* ```
*
* the constant `Baz` has the fully qualified name `Foo::Bar::Baz`, and
* `CONST_A` has the fully qualified name `Foo::CONST_A`.
*
* Important note: This can return more than one value, because there are
* situations where there can be multiple possible "fully qualified" names.
* For example:
* ```
* module Mod4
* include Mod1
* module Mod3::Mod5 end
* end
* ```
* In the above snippet, `Mod5` has two valid fully qualified names it can be
* referred to by: `Mod1::Mod3::Mod5`, or `Mod4::Mod3::Mod5`.
*
* Another example has to do with the order in which module definitions are
* executed at runtime. Because of the way that ruby dynamically looks up
* 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) }
}
/**
* A definition of a constant via assignment. For example, the left-hand
* operand in the following example:
*
* ```rb
* MAX_SIZE = 100
* ```
*/
class ConstantAssignment extends ConstantWriteAccess, LhsExpr {
override string getAPrimaryQlClass() { result = "ConstantAssignment" }
}