mirror of
https://github.com/github/codeql.git
synced 2026-02-20 08:53:49 +01:00
Add and expand AST classes for literals
This commit is contained in:
@@ -3,6 +3,7 @@ import ast.Call
|
||||
import ast.Control
|
||||
import ast.Constant
|
||||
import ast.Expr
|
||||
import ast.Literal
|
||||
import ast.Method
|
||||
import ast.Module
|
||||
import ast.Parameter
|
||||
|
||||
@@ -24,115 +24,7 @@ class Self extends Expr, @token_self {
|
||||
final override string getAPrimaryQlClass() { result = "Self" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A literal.
|
||||
*
|
||||
* This is the QL root class for all literals.
|
||||
*/
|
||||
class Literal extends Expr {
|
||||
override Literal::Range range;
|
||||
|
||||
/** Gets the source text for this literal, if it is constant. */
|
||||
final string getValueText() { result = range.getValueText() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An integer literal.
|
||||
* ```rb
|
||||
* x = 123
|
||||
* y = 0xff
|
||||
* ```
|
||||
*/
|
||||
class IntegerLiteral extends Literal, @token_integer {
|
||||
final override IntegerLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "IntegerLiteral" }
|
||||
}
|
||||
|
||||
/** A `nil` literal. */
|
||||
class NilLiteral extends Literal, @token_nil {
|
||||
final override NilLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "NilLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Boolean literal.
|
||||
* ```rb
|
||||
* true
|
||||
* false
|
||||
* TRUE
|
||||
* FALSE
|
||||
* ```
|
||||
*/
|
||||
class BooleanLiteral extends Literal, BooleanLiteral::DbUnion {
|
||||
final override BooleanLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "BooleanLiteral" }
|
||||
|
||||
/** Holds if the Boolean literal is `true` or `TRUE`. */
|
||||
predicate isTrue() { range.isTrue() }
|
||||
|
||||
/** Holds if the Boolean literal is `false` or `FALSE`. */
|
||||
predicate isFalse() { range.isFalse() }
|
||||
}
|
||||
|
||||
// TODO: expand this. It's a minimal placeholder so we can test `=~` and `!~`.
|
||||
class RegexLiteral extends Literal, @regex {
|
||||
final override RegexLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "RegexLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A string literal.
|
||||
* ```rb
|
||||
* 'hello'
|
||||
* "hello, #{name}"
|
||||
* ```
|
||||
* TODO: expand this minimal placeholder.
|
||||
*/
|
||||
class StringLiteral extends Literal, @string__ {
|
||||
final override StringLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "StringLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A symbol literal.
|
||||
* ```rb
|
||||
* :foo
|
||||
* :"foo bar"
|
||||
* :"foo bar #{baz}"
|
||||
* ```
|
||||
* TODO: expand this minimal placeholder.
|
||||
*/
|
||||
class SymbolLiteral extends Literal {
|
||||
final override SymbolLiteral::Range range;
|
||||
|
||||
SymbolLiteral() {
|
||||
not any(UndefStmt u).getAMethodName() = this and
|
||||
not any(AliasStmt a).getNewName() = this and
|
||||
not any(AliasStmt a).getOldName() = this
|
||||
}
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "SymbolLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method name literal. For example:
|
||||
* ```rb
|
||||
* - method_name # a normal name
|
||||
* - + # an operator
|
||||
* - :method_name # a symbol
|
||||
* - :"eval_#{name}" # a complex symbol
|
||||
* ```
|
||||
*/
|
||||
class MethodName extends Literal {
|
||||
final override MethodName::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "MethodName" }
|
||||
}
|
||||
|
||||
/** A sequence of expressions. */
|
||||
class StmtSequence extends Expr {
|
||||
@@ -322,3 +214,52 @@ class RescueModifierExpr extends Expr, @rescue_modifier {
|
||||
*/
|
||||
final Stmt getHandler() { result = range.getHandler() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A concatenation of string literals.
|
||||
*
|
||||
* ```rb
|
||||
* "foo" "bar" "baz"
|
||||
* ```
|
||||
*/
|
||||
class StringConcatenation extends Expr, @chained_string {
|
||||
final override StringConcatenation::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "StringConcatenation" }
|
||||
|
||||
/** Gets the `n`th string literal in this concatenation. */
|
||||
final StringLiteral getString(int n) { result = range.getString(n) }
|
||||
|
||||
/** Gets a string literal in this concatenation. */
|
||||
final StringLiteral getAString() { result = this.getString(_) }
|
||||
|
||||
/** Gets the number of string literals in this concatenation. */
|
||||
final int getNumberOfStrings() { result = count(this.getString(_)) }
|
||||
|
||||
/**
|
||||
* Gets the result of concatenating all the string literals, if and only if
|
||||
* they do not contain any interpolations.
|
||||
*
|
||||
* For the following example, the result is `"foobar"`:
|
||||
*
|
||||
* ```rb
|
||||
* "foo" 'bar'
|
||||
* ```
|
||||
*
|
||||
* And for the following example, where one of the string literals includes
|
||||
* an interpolation, there is no result:
|
||||
*
|
||||
* ```rb
|
||||
* "foo" "bar#{ n }"
|
||||
* ```
|
||||
*/
|
||||
final string getConcatenatedValueText() {
|
||||
forall(StringLiteral c | c = this.getString(_) | exists(c.getValueText())) and
|
||||
result =
|
||||
concat(string valueText, int i |
|
||||
valueText = this.getString(i).getValueText()
|
||||
|
|
||||
valueText order by i
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
410
ql/src/codeql_ruby/ast/Literal.qll
Normal file
410
ql/src/codeql_ruby/ast/Literal.qll
Normal file
@@ -0,0 +1,410 @@
|
||||
private import codeql_ruby.AST
|
||||
private import internal.Literal
|
||||
|
||||
/**
|
||||
* A literal.
|
||||
*
|
||||
* This is the QL root class for all literals.
|
||||
*/
|
||||
class Literal extends Expr {
|
||||
override Literal::Range range;
|
||||
|
||||
/**
|
||||
* Gets the source text for this literal, if this is a simple literal.
|
||||
*
|
||||
* For complex literals, such as arrays, hashes, and strings with
|
||||
* interpolations, this predicate has no result.
|
||||
*/
|
||||
final string getValueText() { result = range.getValueText() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A numeric literal, i.e. an integer, floating-point, rational, or complex
|
||||
* value.
|
||||
*
|
||||
* ```rb
|
||||
* 123
|
||||
* 0xff
|
||||
* 3.14159
|
||||
* 1.0E2
|
||||
* 7r
|
||||
* 1i
|
||||
* ```
|
||||
*/
|
||||
class NumericLiteral extends Literal {
|
||||
override NumericLiteral::Range range;
|
||||
}
|
||||
|
||||
/**
|
||||
* An integer literal.
|
||||
*
|
||||
* ```rb
|
||||
* 123
|
||||
* 0xff
|
||||
* ```
|
||||
*/
|
||||
class IntegerLiteral extends NumericLiteral, @token_integer {
|
||||
final override IntegerLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "IntegerLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A floating-point literal.
|
||||
*
|
||||
* ```rb
|
||||
* 1.3
|
||||
* 2.7e+5
|
||||
* ```
|
||||
*/
|
||||
class FloatLiteral extends NumericLiteral, @token_float {
|
||||
final override FloatLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "FloatLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A rational literal.
|
||||
*
|
||||
* ```rb
|
||||
* 123r
|
||||
* ```
|
||||
*/
|
||||
class RationalLiteral extends NumericLiteral, @rational {
|
||||
final override RationalLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "RationalLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A complex literal.
|
||||
*
|
||||
* ```rb
|
||||
* 1i
|
||||
* ```
|
||||
*/
|
||||
class ComplexLiteral extends NumericLiteral, @token_complex {
|
||||
final override ComplexLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "ComplexLiteral" }
|
||||
}
|
||||
|
||||
/** A `nil` literal. */
|
||||
class NilLiteral extends Literal, @token_nil {
|
||||
final override NilLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "NilLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Boolean literal.
|
||||
* ```rb
|
||||
* true
|
||||
* false
|
||||
* TRUE
|
||||
* FALSE
|
||||
* ```
|
||||
*/
|
||||
class BooleanLiteral extends Literal, BooleanLiteral::DbUnion {
|
||||
final override BooleanLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "BooleanLiteral" }
|
||||
|
||||
/** Holds if the Boolean literal is `true` or `TRUE`. */
|
||||
predicate isTrue() { range.isTrue() }
|
||||
|
||||
/** Holds if the Boolean literal is `false` or `FALSE`. */
|
||||
predicate isFalse() { range.isFalse() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for a component of a string: `StringTextComponent`,
|
||||
* `StringEscapeSequenceComponent`, or `StringInterpolationComponent`.
|
||||
*/
|
||||
class StringComponent extends AstNode {
|
||||
override StringComponent::Range range;
|
||||
|
||||
/**
|
||||
* Gets the source text for this string component. Has no result if this is
|
||||
* a `StringInterpolationComponent`.
|
||||
*/
|
||||
final string getValueText() { result = range.getValueText() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A component of a string (or string-like) literal that is simply text.
|
||||
*
|
||||
* For example, the following string literals all contain `StringTextComponent`
|
||||
* components whose `getValueText()` returns `"foo"`:
|
||||
*
|
||||
* ```rb
|
||||
* 'foo'
|
||||
* "#{ bar() }foo"
|
||||
* "foo#{ bar() } baz"
|
||||
* ```
|
||||
*/
|
||||
class StringTextComponent extends StringComponent, @token_string_content {
|
||||
final override StringTextComponent::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "StringTextComponent" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An escape sequence component of a string or string-like literal.
|
||||
*/
|
||||
class StringEscapeSequenceComponent extends StringComponent, @token_escape_sequence {
|
||||
final override StringEscapeSequenceComponent::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "StringEscapeSequenceComponent" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An interpolation expression component of a string or string-like literal.
|
||||
*/
|
||||
class StringInterpolationComponent extends StringComponent, StmtSequence, @interpolation {
|
||||
final override StringInterpolationComponent::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "StringInterpolationComponent" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A string, symbol, regex, or subshell literal.
|
||||
*/
|
||||
class StringlikeLiteral extends Literal {
|
||||
override StringlikeLiteral::Range range;
|
||||
|
||||
/**
|
||||
* Gets the `n`th component of this string or string-like literal. The result
|
||||
* will be one of `StringTextComponent`, `StringInterpolationComponent`, and
|
||||
* `StringEscapeSequenceComponent`.
|
||||
*
|
||||
* In the following example, the result for `n = 0` is the
|
||||
* `StringTextComponent` for `foo_`, and the result for `n = 1` is the
|
||||
* `StringInterpolationComponent` for `Time.now`.
|
||||
*
|
||||
* ```rb
|
||||
* "foo_#{ Time.now }"
|
||||
* ```
|
||||
*/
|
||||
final StringComponent getComponent(int n) { result = range.getComponent(n) }
|
||||
|
||||
/**
|
||||
* Gets the number of components in this string or string-like literal.
|
||||
*
|
||||
* For the empty string `""`, the result is 0.
|
||||
*
|
||||
* For the string `"foo"`, the result is 1: there is a single
|
||||
* `StringTextComponent`.
|
||||
*
|
||||
* For the following example, the result is 3: there is a
|
||||
* `StringTextComponent` for the substring `"foo_"`; a
|
||||
* `StringEscapeSequenceComponent` for the escaped quote; and a
|
||||
* `StringInterpolationComponent` for the interpolation.
|
||||
*
|
||||
* ```rb
|
||||
* "foo\"#{bar}"
|
||||
* ```
|
||||
*/
|
||||
final int getNumberOfComponents() { result = count(this.getComponent(_)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A string literal.
|
||||
*
|
||||
* ```rb
|
||||
* 'hello'
|
||||
* "hello, #{name}"
|
||||
* ```
|
||||
*/
|
||||
class StringLiteral extends StringlikeLiteral {
|
||||
final override StringLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "StringLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A regular expression literal.
|
||||
*
|
||||
* ```rb
|
||||
* /[a-z]+/
|
||||
* ```
|
||||
*/
|
||||
class RegexLiteral extends StringlikeLiteral, @regex {
|
||||
final override RegexLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "RegexLiteral" }
|
||||
|
||||
/**
|
||||
* Gets the regex flags as a string.
|
||||
*
|
||||
* ```rb
|
||||
* /foo/ # => ""
|
||||
* /foo/i # => "i"
|
||||
* /foo/imxo # => "imxo"
|
||||
*/
|
||||
final string getFlagString() { result = range.getFlagString() }
|
||||
|
||||
/**
|
||||
* Holds if the regex was specified using the `i` flag to indicate case
|
||||
* insensitivity, as in the following example:
|
||||
*
|
||||
* ```rb
|
||||
* /foo/i
|
||||
* ```
|
||||
*/
|
||||
final predicate hasCaseInsensitiveFlag() { this.getFlagString().charAt(_) = "i" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A symbol literal.
|
||||
*
|
||||
* ```rb
|
||||
* :foo
|
||||
* :"foo bar"
|
||||
* :"foo bar #{baz}"
|
||||
* ```
|
||||
*/
|
||||
class SymbolLiteral extends StringlikeLiteral {
|
||||
final override SymbolLiteral::Range range;
|
||||
|
||||
SymbolLiteral() {
|
||||
not any(UndefStmt u).getAMethodName() = this and
|
||||
not any(AliasStmt a).getNewName() = this and
|
||||
not any(AliasStmt a).getOldName() = this
|
||||
}
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "SymbolLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A subshell literal.
|
||||
*
|
||||
* ```rb
|
||||
* `ls -l`
|
||||
* %x(/bin/sh foo.sh)
|
||||
* ```
|
||||
*/
|
||||
class SubshellLiteral extends StringlikeLiteral, @subshell {
|
||||
final override SubshellLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "SubshellLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A character literal.
|
||||
*
|
||||
* ```rb
|
||||
* ?a
|
||||
* ?\u{61}
|
||||
* ```
|
||||
*/
|
||||
class CharacterLiteral extends Literal, @token_character {
|
||||
final override CharacterLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "CharacterLiteral" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An array literal.
|
||||
*
|
||||
* ```rb
|
||||
* [123, 'foo', bar()]
|
||||
* %w(foo bar)
|
||||
* %i(foo bar)
|
||||
* ```
|
||||
*/
|
||||
class ArrayLiteral extends Literal {
|
||||
final override ArrayLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "ArrayLiteral" }
|
||||
|
||||
/** Gets the `n`th element in this array literal. */
|
||||
final Expr getElement(int n) { result = range.getElement(n) }
|
||||
|
||||
/** Gets an element in this array literal. */
|
||||
final Expr getAnElement() { result = range.getElement(_) }
|
||||
|
||||
/** Gets the number of elements in this array literal. */
|
||||
final int getNumberOfElements() { result = count(range.getElement(_)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A hash literal.
|
||||
*
|
||||
* ```rb
|
||||
* { foo: 123, bar: 456 }
|
||||
* ```
|
||||
*/
|
||||
class HashLiteral extends Literal, @hash {
|
||||
final override HashLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "HashLiteral" }
|
||||
|
||||
/**
|
||||
* Gets the `n`th element in this array literal.
|
||||
*
|
||||
* In the following example, the 0th element is a `Pair`, and the 1st element
|
||||
* is a `HashSplatArgument`.
|
||||
*
|
||||
* ```rb
|
||||
* { foo: 123, **bar }
|
||||
* ```
|
||||
*/
|
||||
final Expr getElement(int n) { result = range.getElement(n) }
|
||||
|
||||
/** Gets an element in this array literal. */
|
||||
final Expr getAnElement() { result = range.getElement(_) }
|
||||
|
||||
/** Gets a key-value `Pair` in this hash literal. */
|
||||
final Pair getAKeyValuePair() { result = this.getAnElement() }
|
||||
|
||||
/** Gets the number of elements in this hash literal. */
|
||||
final int getNumberOfElements() { result = count(range.getElement(_)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A range literal.
|
||||
*
|
||||
* ```rb
|
||||
* (1..10)
|
||||
* (1024...2048)
|
||||
* ```
|
||||
*/
|
||||
class RangeLiteral extends Literal, @range {
|
||||
final override RangeLiteral::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "RangeLiteral" }
|
||||
|
||||
/** Gets the begin expression of this range, if any. */
|
||||
final Expr getBegin() { result = range.getBegin() }
|
||||
|
||||
/** Gets the end expression of this range, if any. */
|
||||
final Expr getEnd() { result = range.getEnd() }
|
||||
|
||||
/**
|
||||
* Holds if the range is inclusive of the end value, i.e. uses the `..`
|
||||
* operator.
|
||||
*/
|
||||
final predicate isInclusive() { range.isInclusive() }
|
||||
|
||||
/**
|
||||
* Holds if the range is exclusive of the end value, i.e. uses the `...`
|
||||
* operator.
|
||||
*/
|
||||
final predicate isExclusive() { range.isExclusive() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method name literal. For example:
|
||||
* ```rb
|
||||
* method_name # a normal name
|
||||
* + # an operator
|
||||
* :method_name # a symbol
|
||||
* :"eval_#{name}" # a complex symbol
|
||||
* ```
|
||||
*/
|
||||
class MethodName extends Literal {
|
||||
final override MethodName::Range range;
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "MethodName" }
|
||||
}
|
||||
@@ -31,37 +31,13 @@ module AstNode {
|
||||
or
|
||||
this = any(Generated::RestAssignment ra).getChild()
|
||||
or
|
||||
this instanceof Generated::SymbolArray
|
||||
or
|
||||
this instanceof Generated::Interpolation
|
||||
or
|
||||
this instanceof Generated::StringArray
|
||||
or
|
||||
this instanceof Generated::BareString
|
||||
or
|
||||
this instanceof Generated::Float
|
||||
or
|
||||
this instanceof Generated::Superclass
|
||||
or
|
||||
this instanceof Generated::Hash
|
||||
or
|
||||
this instanceof Generated::Array
|
||||
or
|
||||
this instanceof Generated::Complex
|
||||
or
|
||||
this instanceof Generated::Character
|
||||
or
|
||||
this instanceof Generated::HeredocBody
|
||||
or
|
||||
this instanceof Generated::HeredocBeginning
|
||||
or
|
||||
this instanceof Generated::HeredocEnd
|
||||
or
|
||||
this instanceof Generated::Range
|
||||
or
|
||||
this instanceof Generated::Rational
|
||||
or
|
||||
this instanceof Generated::Subshell
|
||||
}
|
||||
|
||||
override string toString() { result = "AstNode" }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
private import codeql_ruby.AST
|
||||
private import codeql_ruby.ast.internal.Literal
|
||||
private import codeql_ruby.ast.internal.Statement
|
||||
private import codeql_ruby.ast.internal.TreeSitter
|
||||
|
||||
@@ -14,194 +15,6 @@ module Self {
|
||||
}
|
||||
}
|
||||
|
||||
module Literal {
|
||||
abstract class Range extends Expr::Range {
|
||||
abstract string getValueText();
|
||||
|
||||
override string toString() { result = this.getValueText() }
|
||||
}
|
||||
}
|
||||
|
||||
module IntegerLiteral {
|
||||
class Range extends Literal::Range, @token_integer {
|
||||
final override Generated::Integer generated;
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = this.getValueText() }
|
||||
}
|
||||
}
|
||||
|
||||
module NilLiteral {
|
||||
class Range extends Literal::Range, @token_nil {
|
||||
final override Generated::Nil generated;
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = this.getValueText() }
|
||||
}
|
||||
}
|
||||
|
||||
module BooleanLiteral {
|
||||
class DbUnion = @token_true or @token_false;
|
||||
|
||||
class Range extends Literal::Range, DbUnion {
|
||||
final override Generated::Token generated;
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = this.getValueText() }
|
||||
|
||||
predicate isTrue() { this instanceof @token_true }
|
||||
|
||||
predicate isFalse() { this instanceof @token_false }
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: expand this. It's a minimal placeholder so we can test `=~` and `!~`.
|
||||
module RegexLiteral {
|
||||
class Range extends Literal::Range, @regex {
|
||||
final override Generated::Regex generated;
|
||||
|
||||
final override string getValueText() {
|
||||
forall(AstNode n | n = generated.getChild(_) | n instanceof Generated::Token) and
|
||||
result =
|
||||
concat(int i, string s |
|
||||
s = generated.getChild(i).(Generated::Token).getValue()
|
||||
|
|
||||
s order by i
|
||||
)
|
||||
}
|
||||
|
||||
final override string toString() {
|
||||
result =
|
||||
concat(Generated::AstNode c, int i, string s |
|
||||
c = generated.getChild(i) and
|
||||
if c instanceof Generated::Token
|
||||
then s = c.(Generated::Token).getValue()
|
||||
else s = "#{...}"
|
||||
|
|
||||
s order by i
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: expand this minimal placeholder.
|
||||
module StringLiteral {
|
||||
class Range extends Literal::Range, @string__ {
|
||||
final override Generated::String generated;
|
||||
|
||||
final override string getValueText() {
|
||||
strictcount(generated.getChild(_)) = 1 and
|
||||
result = generated.getChild(0).(Generated::Token).getValue()
|
||||
}
|
||||
|
||||
final override string toString() {
|
||||
result =
|
||||
concat(Generated::AstNode c, int i, string s |
|
||||
c = generated.getChild(i) and
|
||||
if c instanceof Generated::Token
|
||||
then s = c.(Generated::Token).getValue()
|
||||
else s = "#{...}"
|
||||
|
|
||||
s order by i
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: expand this minimal placeholder.
|
||||
module SymbolLiteral {
|
||||
abstract class Range extends Literal::Range { }
|
||||
|
||||
class SimpleSymbolRange extends SymbolLiteral::Range {
|
||||
final override Generated::SimpleSymbol generated;
|
||||
|
||||
// Tree-sitter gives us value text including the colon, which we skip.
|
||||
final override string getValueText() { result = generated.getValue().suffix(1) }
|
||||
|
||||
final override string toString() { result = generated.getValue() }
|
||||
}
|
||||
|
||||
abstract private class ComplexSymbolRange extends SymbolLiteral::Range {
|
||||
abstract Generated::AstNode getChild(int i);
|
||||
|
||||
final override string getValueText() {
|
||||
strictcount(this.getChild(_)) = 1 and
|
||||
result = this.getChild(0).(Generated::Token).getValue()
|
||||
}
|
||||
|
||||
private string summaryString() {
|
||||
result =
|
||||
concat(Generated::AstNode c, int i, string s |
|
||||
c = this.getChild(i) and
|
||||
if c instanceof Generated::Token
|
||||
then s = c.(Generated::Token).getValue()
|
||||
else s = "#{...}"
|
||||
|
|
||||
s order by i
|
||||
)
|
||||
}
|
||||
|
||||
final override string toString() {
|
||||
if summaryString().regexpMatch("[a-zA-z_][a-zA-Z_0-9]*")
|
||||
then result = ":" + summaryString()
|
||||
else result = ":\"" + summaryString() + "\""
|
||||
}
|
||||
}
|
||||
|
||||
class DelimitedSymbolRange extends ComplexSymbolRange, @delimited_symbol {
|
||||
final override Generated::DelimitedSymbol generated;
|
||||
|
||||
final override Generated::AstNode getChild(int i) { result = generated.getChild(i) }
|
||||
}
|
||||
|
||||
class BareSymbolRange extends ComplexSymbolRange, @bare_symbol {
|
||||
final override Generated::BareSymbol generated;
|
||||
|
||||
final override Generated::AstNode getChild(int i) { result = generated.getChild(i) }
|
||||
}
|
||||
|
||||
class HashKeySymbolRange extends SymbolLiteral::Range, @token_hash_key_symbol {
|
||||
final override Generated::HashKeySymbol generated;
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = ":" + this.getValueText() }
|
||||
}
|
||||
}
|
||||
|
||||
module MethodName {
|
||||
private class TokenTypes =
|
||||
@setter or @token_class_variable or @token_constant or @token_global_variable or
|
||||
@token_identifier or @token_instance_variable or @token_operator;
|
||||
|
||||
abstract class Range extends Literal::Range, @underscore_method_name {
|
||||
Range() {
|
||||
exists(Generated::Undef u | u.getChild(_) = generated)
|
||||
or
|
||||
exists(Generated::Alias a | a.getName() = generated or a.getAlias() = generated)
|
||||
}
|
||||
}
|
||||
|
||||
private class TokenMethodName extends MethodName::Range, TokenTypes {
|
||||
final override Generated::UnderscoreMethodName generated;
|
||||
|
||||
final override string getValueText() {
|
||||
result = generated.(Generated::Token).getValue()
|
||||
or
|
||||
result = generated.(Generated::Setter).getName().getValue() + "="
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleSymbolMethodName extends MethodName::Range, SymbolLiteral::SimpleSymbolRange,
|
||||
@token_simple_symbol { }
|
||||
|
||||
private class DelimitedSymbolMethodName extends MethodName::Range,
|
||||
SymbolLiteral::DelimitedSymbolRange, @delimited_symbol { }
|
||||
}
|
||||
|
||||
module StmtSequence {
|
||||
abstract class Range extends Expr::Range {
|
||||
abstract Stmt getStmt(int n);
|
||||
@@ -343,3 +156,13 @@ module Pair {
|
||||
final override string toString() { result = "Pair" }
|
||||
}
|
||||
}
|
||||
|
||||
module StringConcatenation {
|
||||
class Range extends Expr::Range, @chained_string {
|
||||
final override Generated::ChainedString generated;
|
||||
|
||||
final StringLiteral::Range getString(int i) { result = generated.getChild(i) }
|
||||
|
||||
final override string toString() { result = "\"...\" \"...\"" }
|
||||
}
|
||||
}
|
||||
|
||||
373
ql/src/codeql_ruby/ast/internal/Literal.qll
Normal file
373
ql/src/codeql_ruby/ast/internal/Literal.qll
Normal file
@@ -0,0 +1,373 @@
|
||||
private import codeql_ruby.AST
|
||||
private import codeql_ruby.ast.internal.AST
|
||||
private import codeql_ruby.ast.internal.Expr
|
||||
private import codeql_ruby.ast.internal.TreeSitter
|
||||
|
||||
module Literal {
|
||||
abstract class Range extends Expr::Range {
|
||||
abstract string getValueText();
|
||||
|
||||
override string toString() { result = this.getValueText() }
|
||||
}
|
||||
}
|
||||
|
||||
module NumericLiteral {
|
||||
abstract class Range extends Literal::Range { }
|
||||
}
|
||||
|
||||
module IntegerLiteral {
|
||||
class Range extends NumericLiteral::Range, @token_integer {
|
||||
final override Generated::Integer generated;
|
||||
|
||||
Range() { not any(Generated::Rational r).getChild() = this }
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = this.getValueText() }
|
||||
}
|
||||
}
|
||||
|
||||
module FloatLiteral {
|
||||
class Range extends NumericLiteral::Range, @token_float {
|
||||
final override Generated::Float generated;
|
||||
|
||||
Range() { not any(Generated::Rational r).getChild() = this }
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = this.getValueText() }
|
||||
}
|
||||
}
|
||||
|
||||
module RationalLiteral {
|
||||
class Range extends NumericLiteral::Range, @rational {
|
||||
final override Generated::Rational generated;
|
||||
|
||||
final override string getValueText() {
|
||||
result = generated.getChild().(Generated::Token).getValue()
|
||||
}
|
||||
|
||||
final override string toString() { result = this.getValueText() + "r" }
|
||||
}
|
||||
}
|
||||
|
||||
module ComplexLiteral {
|
||||
class Range extends NumericLiteral::Range, @token_complex {
|
||||
final override Generated::Complex generated;
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = this.getValueText() }
|
||||
}
|
||||
}
|
||||
|
||||
module NilLiteral {
|
||||
class Range extends Literal::Range, @token_nil {
|
||||
final override Generated::Nil generated;
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = this.getValueText() }
|
||||
}
|
||||
}
|
||||
|
||||
module BooleanLiteral {
|
||||
class DbUnion = @token_true or @token_false;
|
||||
|
||||
class Range extends Literal::Range, DbUnion {
|
||||
final override Generated::Token generated;
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = this.getValueText() }
|
||||
|
||||
predicate isTrue() { this instanceof @token_true }
|
||||
|
||||
predicate isFalse() { this instanceof @token_false }
|
||||
}
|
||||
}
|
||||
|
||||
module StringComponent {
|
||||
abstract class Range extends AstNode::Range {
|
||||
abstract string getValueText();
|
||||
}
|
||||
}
|
||||
|
||||
module StringTextComponent {
|
||||
class Range extends StringComponent::Range, @token_string_content {
|
||||
final override Generated::StringContent generated;
|
||||
|
||||
final override string toString() { result = generated.getValue() }
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
}
|
||||
}
|
||||
|
||||
module StringEscapeSequenceComponent {
|
||||
class Range extends StringComponent::Range, @token_escape_sequence {
|
||||
final override Generated::EscapeSequence generated;
|
||||
|
||||
final override string toString() { result = generated.getValue() }
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
}
|
||||
}
|
||||
|
||||
module StringInterpolationComponent {
|
||||
class Range extends StringComponent::Range, StmtSequence::Range, @interpolation {
|
||||
final override Generated::Interpolation generated;
|
||||
|
||||
final override string toString() { result = "#{...}" }
|
||||
|
||||
final override Expr getStmt(int n) {
|
||||
// TODO: fix grammar to properly handle a sequence of more than one expr,
|
||||
// e.g. #{ foo; bar }
|
||||
n = 0 and
|
||||
result = generated.getChild()
|
||||
}
|
||||
|
||||
final override string getValueText() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
module StringlikeLiteral {
|
||||
abstract class Range extends Literal::Range {
|
||||
abstract StringComponent::Range getComponent(int i);
|
||||
|
||||
string getStartDelimiter() { result = "" }
|
||||
|
||||
string getEndDelimiter() { result = "" }
|
||||
|
||||
final predicate isSimple() { count(this.getComponent(_)) <= 1 }
|
||||
|
||||
override string getValueText() {
|
||||
// 0 components should result in the empty string
|
||||
// if there are any interpolations, there should be no result
|
||||
// otherwise, concatenate all the components
|
||||
forall(StringComponent c | c = this.getComponent(_) |
|
||||
not c instanceof StringInterpolationComponent::Range
|
||||
) and
|
||||
result =
|
||||
concat(StringComponent::Range c, int i |
|
||||
c = this.getComponent(i)
|
||||
|
|
||||
c.getValueText() order by i
|
||||
)
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
result =
|
||||
this.getStartDelimiter() +
|
||||
concat(StringComponent::Range c, int i, string s |
|
||||
c = this.getComponent(i) and
|
||||
if c instanceof Generated::Token
|
||||
then s = c.(Generated::Token).getValue()
|
||||
else s = "#{...}"
|
||||
|
|
||||
s order by i
|
||||
) + this.getEndDelimiter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module StringLiteral {
|
||||
abstract class Range extends StringlikeLiteral::Range {
|
||||
final override string getStartDelimiter() { result = "\"" }
|
||||
|
||||
final override string getEndDelimiter() { result = "\"" }
|
||||
}
|
||||
|
||||
private class RegularStringRange extends StringLiteral::Range, @string__ {
|
||||
final override Generated::String generated;
|
||||
|
||||
final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) }
|
||||
}
|
||||
|
||||
private class BareStringRange extends StringLiteral::Range, @bare_string {
|
||||
final override Generated::BareString generated;
|
||||
|
||||
final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) }
|
||||
}
|
||||
}
|
||||
|
||||
module RegexLiteral {
|
||||
class Range extends StringlikeLiteral::Range, @regex {
|
||||
final override Generated::Regex generated;
|
||||
|
||||
final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) }
|
||||
|
||||
final override string getStartDelimiter() { result = "/" }
|
||||
|
||||
final override string getEndDelimiter() { result = "/" }
|
||||
|
||||
final string getFlagString() {
|
||||
// For `/foo/i`, there should be an `/i` token in the database with `this`
|
||||
// as its parents. Strip the delimiter, which can vary.
|
||||
result =
|
||||
max(Generated::Token t |
|
||||
t.getParent() = this
|
||||
|
|
||||
t.getValue().suffix(1) order by t.getParentIndex()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module SymbolLiteral {
|
||||
abstract class Range extends StringlikeLiteral::Range { }
|
||||
|
||||
class SimpleSymbolRange extends SymbolLiteral::Range {
|
||||
final override Generated::SimpleSymbol generated;
|
||||
|
||||
final override StringComponent::Range getComponent(int i) { none() }
|
||||
|
||||
final override string getStartDelimiter() { result = ":" }
|
||||
|
||||
// Tree-sitter gives us value text including the colon, which we skip.
|
||||
final override string getValueText() { result = generated.getValue().suffix(1) }
|
||||
|
||||
final override string toString() { result = generated.getValue() }
|
||||
}
|
||||
|
||||
abstract private class ComplexSymbolRange extends SymbolLiteral::Range {
|
||||
final override string getStartDelimiter() { result = ":\"" }
|
||||
|
||||
final override string getEndDelimiter() { result = "\"" }
|
||||
}
|
||||
|
||||
class DelimitedSymbolRange extends ComplexSymbolRange, @delimited_symbol {
|
||||
final override Generated::DelimitedSymbol generated;
|
||||
|
||||
final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) }
|
||||
}
|
||||
|
||||
class BareSymbolRange extends ComplexSymbolRange, @bare_symbol {
|
||||
final override Generated::BareSymbol generated;
|
||||
|
||||
final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) }
|
||||
}
|
||||
|
||||
class HashKeySymbolRange extends SymbolLiteral::Range, @token_hash_key_symbol {
|
||||
final override Generated::HashKeySymbol generated;
|
||||
|
||||
final override StringComponent::Range getComponent(int i) { none() }
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = ":" + this.getValueText() }
|
||||
}
|
||||
}
|
||||
|
||||
module SubshellLiteral {
|
||||
class Range extends StringlikeLiteral::Range, @subshell {
|
||||
final override Generated::Subshell generated;
|
||||
|
||||
final override StringComponent::Range getComponent(int i) { result = generated.getChild(i) }
|
||||
|
||||
final override string getStartDelimiter() { result = "`" }
|
||||
|
||||
final override string getEndDelimiter() { result = "`" }
|
||||
}
|
||||
}
|
||||
|
||||
module CharacterLiteral {
|
||||
class Range extends Literal::Range, @token_character {
|
||||
final override Generated::Character generated;
|
||||
|
||||
final override string getValueText() { result = generated.getValue() }
|
||||
|
||||
final override string toString() { result = generated.getValue() }
|
||||
}
|
||||
}
|
||||
|
||||
module ArrayLiteral {
|
||||
abstract class Range extends Literal::Range {
|
||||
final override string getValueText() { none() }
|
||||
|
||||
abstract Expr getElement(int i);
|
||||
}
|
||||
|
||||
private class RegularArrayRange extends ArrayLiteral::Range, @array {
|
||||
final override Generated::Array generated;
|
||||
|
||||
final override Expr getElement(int i) { result = generated.getChild(i) }
|
||||
|
||||
final override string toString() { result = "[...]" }
|
||||
}
|
||||
|
||||
private class StringArrayRange extends ArrayLiteral::Range, @string_array {
|
||||
final override Generated::StringArray generated;
|
||||
|
||||
final override Expr getElement(int i) { result = generated.getChild(i) }
|
||||
|
||||
final override string toString() { result = "%w(...)" }
|
||||
}
|
||||
|
||||
private class SymbolArrayRange extends ArrayLiteral::Range, @symbol_array {
|
||||
final override Generated::SymbolArray generated;
|
||||
|
||||
final override Expr getElement(int i) { result = generated.getChild(i) }
|
||||
|
||||
final override string toString() { result = "%i(...)" }
|
||||
}
|
||||
}
|
||||
|
||||
module HashLiteral {
|
||||
class Range extends Literal::Range, @hash {
|
||||
final override Generated::Hash generated;
|
||||
|
||||
final override string getValueText() { none() }
|
||||
|
||||
final Expr getElement(int i) { result = generated.getChild(i) }
|
||||
|
||||
final override string toString() { result = "{...}" }
|
||||
}
|
||||
}
|
||||
|
||||
module RangeLiteral {
|
||||
class Range extends Literal::Range, @range {
|
||||
final override Generated::Range generated;
|
||||
|
||||
final override string getValueText() { none() }
|
||||
|
||||
final override string toString() { result = "_ " + generated.getOperator() + " _" }
|
||||
|
||||
final Expr getBegin() { result = generated.getBegin() }
|
||||
|
||||
final Expr getEnd() { result = generated.getEnd() }
|
||||
|
||||
final predicate isInclusive() { this instanceof @range_dotdot }
|
||||
|
||||
final predicate isExclusive() { this instanceof @range_dotdotdot }
|
||||
}
|
||||
}
|
||||
|
||||
module MethodName {
|
||||
private class TokenTypes =
|
||||
@setter or @token_class_variable or @token_constant or @token_global_variable or
|
||||
@token_identifier or @token_instance_variable or @token_operator;
|
||||
|
||||
abstract class Range extends Literal::Range, @underscore_method_name {
|
||||
Range() {
|
||||
exists(Generated::Undef u | u.getChild(_) = generated)
|
||||
or
|
||||
exists(Generated::Alias a | a.getName() = generated or a.getAlias() = generated)
|
||||
}
|
||||
}
|
||||
|
||||
private class TokenMethodName extends MethodName::Range, TokenTypes {
|
||||
final override Generated::UnderscoreMethodName generated;
|
||||
|
||||
final override string getValueText() {
|
||||
result = generated.(Generated::Token).getValue()
|
||||
or
|
||||
result = generated.(Generated::Setter).getName().getValue() + "="
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleSymbolMethodName extends MethodName::Range, SymbolLiteral::SimpleSymbolRange,
|
||||
@token_simple_symbol { }
|
||||
|
||||
private class DelimitedSymbolMethodName extends MethodName::Range,
|
||||
SymbolLiteral::DelimitedSymbolRange, @delimited_symbol { }
|
||||
}
|
||||
@@ -1083,15 +1083,25 @@ module Generated {
|
||||
class Range extends @range, AstNode {
|
||||
override string getAPrimaryQlClass() { result = "Range" }
|
||||
|
||||
override Location getLocation() { range_def(this, _, _, result) }
|
||||
override Location getLocation() { range_def(this, _, _, _, result) }
|
||||
|
||||
UnderscoreArg getChild(int i) { range_child(this, i, result) }
|
||||
UnderscoreArg getBegin() { range_begin(this, result) }
|
||||
|
||||
override AstNode getParent() { range_def(this, result, _, _) }
|
||||
UnderscoreArg getEnd() { range_end(this, result) }
|
||||
|
||||
override int getParentIndex() { range_def(this, _, result, _) }
|
||||
string getOperator() {
|
||||
exists(int value | range_def(this, _, _, value, _) |
|
||||
result = ".." and value = 0
|
||||
or
|
||||
result = "..." and value = 1
|
||||
)
|
||||
}
|
||||
|
||||
override AstNode getAFieldOrChild() { range_child(this, _, result) }
|
||||
override AstNode getParent() { range_def(this, result, _, _, _) }
|
||||
|
||||
override int getParentIndex() { range_def(this, _, result, _, _) }
|
||||
|
||||
override AstNode getAFieldOrChild() { range_begin(this, result) or range_end(this, result) }
|
||||
}
|
||||
|
||||
class Rational extends @rational, AstNode {
|
||||
|
||||
@@ -264,7 +264,9 @@ private module Cached {
|
||||
or
|
||||
i = any(Generated::Program x).getChild(_)
|
||||
or
|
||||
i = any(Generated::Range x).getChild(_)
|
||||
i = any(Generated::Range x).getBegin()
|
||||
or
|
||||
i = any(Generated::Range x).getEnd()
|
||||
or
|
||||
i = any(Generated::RescueModifier x).getBody()
|
||||
or
|
||||
|
||||
Reference in New Issue
Block a user