mirror of
https://github.com/github/codeql.git
synced 2026-02-20 00:43:44 +01:00
Merge remote-tracking branch 'origin/main' into cfg_cleanup
This commit is contained in:
@@ -14,6 +14,7 @@ import ast.Statement
|
||||
import ast.Variable
|
||||
private import ast.internal.AST
|
||||
private import ast.internal.Scope
|
||||
private import ast.internal.Synthesis
|
||||
|
||||
/**
|
||||
* A node in the abstract syntax tree. This class is the base class for all Ruby
|
||||
@@ -56,7 +57,11 @@ class AstNode extends TAstNode {
|
||||
final AstNode getAChild() { result = this.getAChild(_) }
|
||||
|
||||
/** Gets the parent of this `AstNode`, if this node is not a root node. */
|
||||
final AstNode getParent() { result.getAChild() = this }
|
||||
final AstNode getParent() {
|
||||
result.getAChild() = this
|
||||
or
|
||||
result.getAChild().getDesugared() = this
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a child of this node, which can also be retrieved using a predicate
|
||||
@@ -75,5 +80,25 @@ class AstNode extends TAstNode {
|
||||
* foo(123)
|
||||
* ```
|
||||
*/
|
||||
predicate isSynthesized() { this instanceof TImplicitSelf }
|
||||
final predicate isSynthesized() { this = getSynthChild(_, _) }
|
||||
|
||||
/**
|
||||
* Gets the desugared version of this AST node, if any.
|
||||
*
|
||||
* For example, the desugared version of
|
||||
*
|
||||
* ```rb
|
||||
* x += y
|
||||
* ```
|
||||
*
|
||||
* is
|
||||
*
|
||||
* ```rb
|
||||
* x = x + y
|
||||
* ```
|
||||
*
|
||||
* when `x` is a variable. Whenever an AST node can be desugared,
|
||||
* then the desugared version is used in the control-flow graph.
|
||||
*/
|
||||
final AstNode getDesugared() { result = getSynthChild(this, -1) }
|
||||
}
|
||||
|
||||
52
ql/src/codeql_ruby/Diagnostics.qll
Normal file
52
ql/src/codeql_ruby/Diagnostics.qll
Normal file
@@ -0,0 +1,52 @@
|
||||
private import codeql.Locations
|
||||
|
||||
/** A diagnostic emitted during extraction, such as a parse error */
|
||||
class Diagnostic extends @diagnostic {
|
||||
int severity;
|
||||
string tag;
|
||||
string message;
|
||||
string fullMessage;
|
||||
Location location;
|
||||
|
||||
Diagnostic() { diagnostics(this, severity, tag, message, fullMessage, location) }
|
||||
|
||||
/**
|
||||
* Gets the numerical severity level associated with this diagnostic.
|
||||
*/
|
||||
int getSeverity() { result = severity }
|
||||
|
||||
/** Gets a string representation of the severity of this diagnostic. */
|
||||
string getSeverityText() {
|
||||
severity = 10 and result = "Debug"
|
||||
or
|
||||
severity = 20 and result = "Info"
|
||||
or
|
||||
severity = 30 and result = "Warning"
|
||||
or
|
||||
severity = 40 and result = "Error"
|
||||
}
|
||||
|
||||
/** Gets the error code associated with this diagnostic, e.g. parse_error. */
|
||||
string getTag() { result = tag }
|
||||
|
||||
/**
|
||||
* Gets the error message text associated with this diagnostic.
|
||||
*/
|
||||
string getMessage() { result = message }
|
||||
|
||||
/**
|
||||
* Gets the full error message text associated with this diagnostic.
|
||||
*/
|
||||
string getFullMessage() { result = fullMessage }
|
||||
|
||||
/** Gets the source location of this diagnostic. */
|
||||
Location getLocation() { result = location }
|
||||
|
||||
/** Gets a textual representation of this diagnostic. */
|
||||
string toString() { result = this.getMessage() }
|
||||
}
|
||||
|
||||
/** A diagnostic relating to a particular error in extracting a file. */
|
||||
class ExtractionError extends Diagnostic, @diagnostic_error {
|
||||
ExtractionError() { this.getTag() = "parse_error" }
|
||||
}
|
||||
@@ -137,7 +137,7 @@ private class IdentifierMethodCall extends MethodCall, TIdentifierMethodCall {
|
||||
|
||||
final override string getMethodName() { result = getMethodName(this, g.getValue()) }
|
||||
|
||||
final override Self getReceiver() { result = TImplicitSelf(g) }
|
||||
final override Self getReceiver() { result = TSelfSynth(this, 0) }
|
||||
}
|
||||
|
||||
private class ScopeResolutionMethodCall extends MethodCall, TScopeResolutionMethodCall {
|
||||
@@ -162,12 +162,7 @@ private class RegularMethodCall extends MethodCall, TRegularMethodCall {
|
||||
not exists(g.getReceiver()) and
|
||||
toGenerated(result) = g.getMethod().(Generated::ScopeResolution).getScope()
|
||||
or
|
||||
// If there's no explicit receiver (or scope resolution that acts like a
|
||||
// receiver), then the receiver is implicitly `self`. N.B. `::Foo()` is
|
||||
// not valid Ruby.
|
||||
not exists(g.getReceiver()) and
|
||||
not exists(g.getMethod().(Generated::ScopeResolution).getScope()) and
|
||||
result = TImplicitSelf(g)
|
||||
result = TSelfSynth(this, 0)
|
||||
}
|
||||
|
||||
final override string getMethodName() {
|
||||
|
||||
@@ -79,6 +79,12 @@ class StmtSequence extends Expr, TStmtSequence {
|
||||
override AstNode getAChild(string pred) { pred = "getStmt" and result = this.getStmt(_) }
|
||||
}
|
||||
|
||||
private class StmtSequenceSynth extends StmtSequence, TStmtSequenceSynth {
|
||||
final override Stmt getStmt(int n) { synthChild(this, n, result) }
|
||||
|
||||
final override string toString() { result = "..." }
|
||||
}
|
||||
|
||||
private class Then extends StmtSequence, TThen {
|
||||
private Generated::Then g;
|
||||
|
||||
@@ -210,11 +216,11 @@ class ParenthesizedExpr extends StmtSequence, TParenthesizedExpr {
|
||||
|
||||
ParenthesizedExpr() { this = TParenthesizedExpr(g) }
|
||||
|
||||
final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "ParenthesizedExpr" }
|
||||
|
||||
final override string toString() { result = "( ... )" }
|
||||
|
||||
final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,6 +47,42 @@ class IntegerLiteral extends NumericLiteral, TIntegerLiteral {
|
||||
|
||||
final override string getValueText() { result = g.getValue() }
|
||||
|
||||
/** Gets the numerical value of this integer literal. */
|
||||
final int getValue() {
|
||||
exists(string s, string values, string str |
|
||||
s = this.getValueText().toLowerCase() and
|
||||
(
|
||||
s.matches("0b%") and
|
||||
values = "01" and
|
||||
str = s.suffix(2)
|
||||
or
|
||||
s.matches("0x%") and
|
||||
values = "0123456789abcdef" and
|
||||
str = s.suffix(2)
|
||||
or
|
||||
s.charAt(0) = "0" and
|
||||
not s.charAt(1) = ["b", "x", "o"] and
|
||||
values = "01234567" and
|
||||
str = s.suffix(1)
|
||||
or
|
||||
s.matches("0o%") and
|
||||
values = "01234567" and
|
||||
str = s.suffix(2)
|
||||
or
|
||||
s.charAt(0) != "0" and values = "0123456789" and str = s
|
||||
)
|
||||
|
|
||||
result =
|
||||
sum(int index, string c, int v, int exp |
|
||||
c = str.replaceAll("_", "").charAt(index) and
|
||||
v = values.indexOf(c.toLowerCase()) and
|
||||
exp = str.replaceAll("_", "").length() - index - 1
|
||||
|
|
||||
v * values.length().pow(exp)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
final override string toString() { result = this.getValueText() }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "IntegerLiteral" }
|
||||
|
||||
@@ -101,12 +101,6 @@ class DefinedExpr extends UnaryOperation, TDefinedExpr {
|
||||
|
||||
/** A binary operation. */
|
||||
class BinaryOperation extends Operation, TBinaryOperation {
|
||||
private Generated::Binary g;
|
||||
|
||||
BinaryOperation() { g = toGenerated(this) }
|
||||
|
||||
final override string getOperator() { result = g.getOperator() }
|
||||
|
||||
final override Expr getAnOperand() {
|
||||
result = this.getLeftOperand() or result = this.getRightOperand()
|
||||
}
|
||||
@@ -122,10 +116,28 @@ class BinaryOperation extends Operation, TBinaryOperation {
|
||||
}
|
||||
|
||||
/** Gets the left operand of this binary operation. */
|
||||
final Stmt getLeftOperand() { toGenerated(result) = g.getLeft() }
|
||||
Stmt getLeftOperand() { none() }
|
||||
|
||||
/** Gets the right operand of this binary operation. */
|
||||
final Stmt getRightOperand() { toGenerated(result) = g.getRight() }
|
||||
Stmt getRightOperand() { none() }
|
||||
}
|
||||
|
||||
private class BinaryOperationReal extends BinaryOperation {
|
||||
private Generated::Binary g;
|
||||
|
||||
BinaryOperationReal() { g = toGenerated(this) }
|
||||
|
||||
final override string getOperator() { result = g.getOperator() }
|
||||
|
||||
final override Stmt getLeftOperand() { toGenerated(result) = g.getLeft() }
|
||||
|
||||
final override Stmt getRightOperand() { toGenerated(result) = g.getRight() }
|
||||
}
|
||||
|
||||
abstract private class BinaryOperationSynth extends BinaryOperation {
|
||||
final override Stmt getLeftOperand() { synthChild(this, 0, result) }
|
||||
|
||||
final override Stmt getRightOperand() { synthChild(this, 1, result) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,6 +155,10 @@ class AddExpr extends BinaryArithmeticOperation, TAddExpr {
|
||||
final override string getAPrimaryQlClass() { result = "AddExpr" }
|
||||
}
|
||||
|
||||
private class AddExprSynth extends AddExpr, BinaryOperationSynth, TAddExprSynth {
|
||||
final override string getOperator() { result = "+" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A subtract expression.
|
||||
* ```rb
|
||||
@@ -153,6 +169,10 @@ class SubExpr extends BinaryArithmeticOperation, TSubExpr {
|
||||
final override string getAPrimaryQlClass() { result = "SubExpr" }
|
||||
}
|
||||
|
||||
private class SubExprSynth extends SubExpr, BinaryOperationSynth, TSubExprSynth {
|
||||
final override string getOperator() { result = "-" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A multiply expression.
|
||||
* ```rb
|
||||
@@ -163,6 +183,10 @@ class MulExpr extends BinaryArithmeticOperation, TMulExpr {
|
||||
final override string getAPrimaryQlClass() { result = "MulExpr" }
|
||||
}
|
||||
|
||||
private class MulExprSynth extends MulExpr, BinaryOperationSynth, TMulExprSynth {
|
||||
final override string getOperator() { result = "*" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A divide expression.
|
||||
* ```rb
|
||||
@@ -173,6 +197,10 @@ class DivExpr extends BinaryArithmeticOperation, TDivExpr {
|
||||
final override string getAPrimaryQlClass() { result = "DivExpr" }
|
||||
}
|
||||
|
||||
private class DivExprSynth extends DivExpr, BinaryOperationSynth, TDivExprSynth {
|
||||
final override string getOperator() { result = "/" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A modulo expression.
|
||||
* ```rb
|
||||
@@ -183,6 +211,10 @@ class ModuloExpr extends BinaryArithmeticOperation, TModuloExpr {
|
||||
final override string getAPrimaryQlClass() { result = "ModuloExpr" }
|
||||
}
|
||||
|
||||
private class ModuloExprSynth extends ModuloExpr, BinaryOperationSynth, TModuloExprSynth {
|
||||
final override string getOperator() { result = "%" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An exponent expression.
|
||||
* ```rb
|
||||
@@ -193,6 +225,10 @@ class ExponentExpr extends BinaryArithmeticOperation, TExponentExpr {
|
||||
final override string getAPrimaryQlClass() { result = "ExponentExpr" }
|
||||
}
|
||||
|
||||
private class ExponentExprSynth extends ExponentExpr, BinaryOperationSynth, TExponentExprSynth {
|
||||
final override string getOperator() { result = "**" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A binary logical operation.
|
||||
*/
|
||||
@@ -209,6 +245,10 @@ class LogicalAndExpr extends BinaryLogicalOperation, TLogicalAndExpr {
|
||||
final override string getAPrimaryQlClass() { result = "LogicalAndExpr" }
|
||||
}
|
||||
|
||||
private class LogicalAndExprSynth extends LogicalAndExpr, BinaryOperationSynth, TLogicalAndExprSynth {
|
||||
final override string getOperator() { result = "&&" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A logical OR operation, using either `or` or `||`.
|
||||
* ```rb
|
||||
@@ -220,6 +260,10 @@ class LogicalOrExpr extends BinaryLogicalOperation, TLogicalOrExpr {
|
||||
final override string getAPrimaryQlClass() { result = "LogicalOrExpr" }
|
||||
}
|
||||
|
||||
private class LogicalOrExprSynth extends LogicalOrExpr, BinaryOperationSynth, TLogicalOrExprSynth {
|
||||
final override string getOperator() { result = "||" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A binary bitwise operation.
|
||||
*/
|
||||
@@ -235,6 +279,10 @@ class LShiftExpr extends BinaryBitwiseOperation, TLShiftExpr {
|
||||
final override string getAPrimaryQlClass() { result = "LShiftExpr" }
|
||||
}
|
||||
|
||||
private class LShiftExprSynth extends LShiftExpr, BinaryOperationSynth, TLShiftExprSynth {
|
||||
final override string getOperator() { result = "<<" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A right-shift operation.
|
||||
* ```rb
|
||||
@@ -245,6 +293,10 @@ class RShiftExpr extends BinaryBitwiseOperation, TRShiftExpr {
|
||||
final override string getAPrimaryQlClass() { result = "RShiftExpr" }
|
||||
}
|
||||
|
||||
private class RShiftExprSynth extends RShiftExpr, BinaryOperationSynth, TRShiftExprSynth {
|
||||
final override string getOperator() { result = ">>" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A bitwise AND operation.
|
||||
* ```rb
|
||||
@@ -255,6 +307,10 @@ class BitwiseAndExpr extends BinaryBitwiseOperation, TBitwiseAndExpr {
|
||||
final override string getAPrimaryQlClass() { result = "BitwiseAndExpr" }
|
||||
}
|
||||
|
||||
private class BitwiseAndSynthExpr extends BitwiseAndExpr, BinaryOperationSynth, TBitwiseAndExprSynth {
|
||||
final override string getOperator() { result = "&" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A bitwise OR operation.
|
||||
* ```rb
|
||||
@@ -265,6 +321,10 @@ class BitwiseOrExpr extends BinaryBitwiseOperation, TBitwiseOrExpr {
|
||||
final override string getAPrimaryQlClass() { result = "BitwiseOrExpr" }
|
||||
}
|
||||
|
||||
private class BitwiseOrSynthExpr extends BitwiseOrExpr, BinaryOperationSynth, TBitwiseOrExprSynth {
|
||||
final override string getOperator() { result = "|" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An XOR (exclusive OR) operation.
|
||||
* ```rb
|
||||
@@ -275,6 +335,10 @@ class BitwiseXorExpr extends BinaryBitwiseOperation, TBitwiseXorExpr {
|
||||
final override string getAPrimaryQlClass() { result = "BitwiseXorExpr" }
|
||||
}
|
||||
|
||||
private class BitwiseXorSynthExpr extends BitwiseXorExpr, BinaryOperationSynth, TBitwiseXorExprSynth {
|
||||
final override string getOperator() { result = "^" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison operation. That is, either an equality operation or a
|
||||
* relational operation.
|
||||
@@ -455,17 +519,25 @@ class Assignment extends Operation, TAssignment {
|
||||
* ```
|
||||
*/
|
||||
class AssignExpr extends Assignment, TAssignExpr {
|
||||
final override string getOperator() { result = "=" }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "AssignExpr" }
|
||||
}
|
||||
|
||||
private class AssignExprReal extends AssignExpr, TAssignExprReal {
|
||||
private Generated::Assignment g;
|
||||
|
||||
AssignExpr() { this = TAssignExpr(g) }
|
||||
AssignExprReal() { this = TAssignExprReal(g) }
|
||||
|
||||
final override Pattern getLeftOperand() { toGenerated(result) = g.getLeft() }
|
||||
|
||||
final override Expr getRightOperand() { toGenerated(result) = g.getRight() }
|
||||
}
|
||||
|
||||
final override string getOperator() { result = "=" }
|
||||
private class AssignExprSynth extends AssignExpr, TAssignExprSynth {
|
||||
final override Pattern getLeftOperand() { synthChild(this, 0, result) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "AssignExpr" }
|
||||
final override Expr getRightOperand() { synthChild(this, 1, result) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,10 +37,7 @@ class TuplePatternParameter extends PatternParameter, TuplePattern, TTuplePatter
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "TuplePatternParameter" }
|
||||
|
||||
override AstNode getAChild(string pred) {
|
||||
result = PatternParameter.super.getAChild(pred) or
|
||||
result = TuplePattern.super.getAChild(pred)
|
||||
}
|
||||
override AstNode getAChild(string pred) { result = TuplePattern.super.getAChild(pred) }
|
||||
}
|
||||
|
||||
/** A named parameter. */
|
||||
@@ -60,8 +57,6 @@ class NamedParameter extends Parameter, TNamedParameter {
|
||||
final VariableAccess getDefiningAccess() { result = this.getVariable().getDefiningAccess() }
|
||||
|
||||
override AstNode getAChild(string pred) {
|
||||
result = Parameter.super.getAChild(pred)
|
||||
or
|
||||
pred = "getDefiningAccess" and
|
||||
result = this.getDefiningAccess()
|
||||
}
|
||||
@@ -75,7 +70,7 @@ class SimpleParameter extends NamedParameter, PatternParameter, VariablePattern,
|
||||
|
||||
final override string getName() { result = g.getValue() }
|
||||
|
||||
final override LocalVariable getVariable() { result = TLocalVariable(_, _, g) }
|
||||
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g) }
|
||||
|
||||
final override LocalVariable getAVariable() { result = this.getVariable() }
|
||||
|
||||
@@ -99,7 +94,7 @@ class BlockParameter extends NamedParameter, TBlockParameter {
|
||||
|
||||
final override string getName() { result = g.getName().getValue() }
|
||||
|
||||
final override LocalVariable getVariable() { result = TLocalVariable(_, _, g.getName()) }
|
||||
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
|
||||
|
||||
final override string toString() { result = "&" + this.getName() }
|
||||
|
||||
@@ -122,7 +117,7 @@ class HashSplatParameter extends NamedParameter, THashSplatParameter {
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "HashSplatParameter" }
|
||||
|
||||
final override LocalVariable getVariable() { result = TLocalVariable(_, _, g.getName()) }
|
||||
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
|
||||
|
||||
final override string toString() { result = "**" + this.getName() }
|
||||
|
||||
@@ -147,7 +142,7 @@ class KeywordParameter extends NamedParameter, TKeywordParameter {
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "KeywordParameter" }
|
||||
|
||||
final override LocalVariable getVariable() { result = TLocalVariable(_, _, g.getName()) }
|
||||
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
|
||||
|
||||
/**
|
||||
* Gets the default value, i.e. the value assigned to the parameter when one
|
||||
@@ -197,7 +192,7 @@ class OptionalParameter extends NamedParameter, TOptionalParameter {
|
||||
*/
|
||||
final Expr getDefaultValue() { toGenerated(result) = g.getValue() }
|
||||
|
||||
final override LocalVariable getVariable() { result = TLocalVariable(_, _, g.getName()) }
|
||||
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
|
||||
|
||||
final override string toString() { result = this.getName() }
|
||||
|
||||
@@ -227,7 +222,7 @@ class SplatParameter extends NamedParameter, TSplatParameter {
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "SplatParameter" }
|
||||
|
||||
final override LocalVariable getVariable() { result = TLocalVariable(_, _, g.getName()) }
|
||||
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g.getName()) }
|
||||
|
||||
final override string toString() { result = "*" + this.getName() }
|
||||
|
||||
|
||||
@@ -7,9 +7,13 @@ private import internal.Variable
|
||||
/** A pattern. */
|
||||
class Pattern extends AstNode {
|
||||
Pattern() {
|
||||
explicitAssignmentNode(toGenerated(this), _) or
|
||||
implicitAssignmentNode(toGenerated(this)) or
|
||||
explicitAssignmentNode(toGenerated(this), _)
|
||||
or
|
||||
implicitAssignmentNode(toGenerated(this))
|
||||
or
|
||||
implicitParameterAssignmentNode(toGenerated(this), _)
|
||||
or
|
||||
this = getSynthChild(any(AssignExpr ae), 0)
|
||||
}
|
||||
|
||||
/** Gets a variable used in (or introduced by) this pattern. */
|
||||
|
||||
@@ -8,21 +8,17 @@ private import internal.Variable
|
||||
|
||||
/** A variable declared in a scope. */
|
||||
class Variable extends TVariable {
|
||||
Variable::Range range;
|
||||
|
||||
Variable() { range = this }
|
||||
|
||||
/** Gets the name of this variable. */
|
||||
final string getName() { result = range.getName() }
|
||||
string getName() { none() }
|
||||
|
||||
/** Gets a textual representation of this variable. */
|
||||
final string toString() { result = this.getName() }
|
||||
|
||||
/** Gets the location of this variable. */
|
||||
final Location getLocation() { result = range.getLocation() }
|
||||
Location getLocation() { none() }
|
||||
|
||||
/** Gets the scope this variable is declared in. */
|
||||
final Scope getDeclaringScope() { toGenerated(result) = range.getDeclaringScope() }
|
||||
Scope getDeclaringScope() { none() }
|
||||
|
||||
/** Gets an access to this variable. */
|
||||
VariableAccess getAnAccess() { result.getVariable() = this }
|
||||
@@ -30,12 +26,10 @@ class Variable extends TVariable {
|
||||
|
||||
/** A local variable. */
|
||||
class LocalVariable extends Variable, TLocalVariable {
|
||||
override LocalVariable::Range range;
|
||||
|
||||
final override LocalVariableAccess getAnAccess() { result.getVariable() = this }
|
||||
override LocalVariableAccess getAnAccess() { none() }
|
||||
|
||||
/** Gets the access where this local variable is first introduced. */
|
||||
VariableAccess getDefiningAccess() { result = range.getDefiningAccess() }
|
||||
VariableAccess getDefiningAccess() { none() }
|
||||
|
||||
/**
|
||||
* Holds if this variable is captured. For example in
|
||||
@@ -55,14 +49,14 @@ class LocalVariable extends Variable, TLocalVariable {
|
||||
}
|
||||
|
||||
/** A global variable. */
|
||||
class GlobalVariable extends Variable, TGlobalVariable {
|
||||
class GlobalVariable extends VariableReal, TGlobalVariable {
|
||||
override GlobalVariable::Range range;
|
||||
|
||||
final override GlobalVariableAccess getAnAccess() { result.getVariable() = this }
|
||||
}
|
||||
|
||||
/** An instance variable. */
|
||||
class InstanceVariable extends Variable, TInstanceVariable {
|
||||
class InstanceVariable extends VariableReal, TInstanceVariable {
|
||||
override InstanceVariable::Range range;
|
||||
|
||||
/** Holds is this variable is a class instance variable. */
|
||||
@@ -72,7 +66,7 @@ class InstanceVariable extends Variable, TInstanceVariable {
|
||||
}
|
||||
|
||||
/** A class variable. */
|
||||
class ClassVariable extends Variable, TClassVariable {
|
||||
class ClassVariable extends VariableReal, TClassVariable {
|
||||
override ClassVariable::Range range;
|
||||
|
||||
final override ClassVariableAccess getAnAccess() { result.getVariable() = this }
|
||||
@@ -95,6 +89,8 @@ class VariableAccess extends Expr, TVariableAccess {
|
||||
*/
|
||||
predicate isExplicitWrite(AstNode assignment) {
|
||||
explicitWriteAccess(toGenerated(this), toGenerated(assignment))
|
||||
or
|
||||
this = assignment.(AssignExpr).getLeftOperand()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,22 +121,12 @@ class VariableWriteAccess extends VariableAccess {
|
||||
|
||||
/** An access to a variable where the value is read. */
|
||||
class VariableReadAccess extends VariableAccess {
|
||||
VariableReadAccess() {
|
||||
not this instanceof VariableWriteAccess
|
||||
or
|
||||
// `x` in `x += y` is considered both a read and a write
|
||||
this = any(AssignOperation a).getLeftOperand()
|
||||
}
|
||||
VariableReadAccess() { not this instanceof VariableWriteAccess }
|
||||
}
|
||||
|
||||
/** An access to a local variable. */
|
||||
class LocalVariableAccess extends VariableAccess, TLocalVariableAccess {
|
||||
private Generated::Identifier g;
|
||||
private LocalVariable v;
|
||||
|
||||
LocalVariableAccess() { this = TLocalVariableAccess(g, v) }
|
||||
|
||||
final override LocalVariable getVariable() { result = v }
|
||||
override LocalVariable getVariable() { none() }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "LocalVariableAccess" }
|
||||
|
||||
@@ -160,8 +146,6 @@ class LocalVariableAccess extends VariableAccess, TLocalVariableAccess {
|
||||
* the access to `x` in the second `puts x` is not.
|
||||
*/
|
||||
final predicate isCapturedAccess() { isCapturedAccess(this) }
|
||||
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
/** An access to a local variable where the value is updated. */
|
||||
@@ -172,16 +156,9 @@ class LocalVariableReadAccess extends LocalVariableAccess, VariableReadAccess {
|
||||
|
||||
/** An access to a global variable. */
|
||||
class GlobalVariableAccess extends VariableAccess, TGlobalVariableAccess {
|
||||
private Generated::GlobalVariable g;
|
||||
private GlobalVariable v;
|
||||
|
||||
GlobalVariableAccess() { this = TGlobalVariableAccess(g, v) }
|
||||
|
||||
final override GlobalVariable getVariable() { result = v }
|
||||
override GlobalVariable getVariable() { none() }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "GlobalVariableAccess" }
|
||||
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
/** An access to a global variable where the value is updated. */
|
||||
@@ -192,28 +169,14 @@ class GlobalVariableReadAccess extends GlobalVariableAccess, VariableReadAccess
|
||||
|
||||
/** An access to an instance variable. */
|
||||
class InstanceVariableAccess extends VariableAccess, TInstanceVariableAccess {
|
||||
private Generated::InstanceVariable g;
|
||||
private InstanceVariable v;
|
||||
|
||||
InstanceVariableAccess() { this = TInstanceVariableAccess(g, v) }
|
||||
|
||||
final override InstanceVariable getVariable() { result = v }
|
||||
override InstanceVariable getVariable() { none() }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "InstanceVariableAccess" }
|
||||
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
/** An access to a class variable. */
|
||||
class ClassVariableAccess extends VariableAccess, TClassVariableAccess {
|
||||
private Generated::ClassVariable g;
|
||||
private ClassVariable v;
|
||||
|
||||
ClassVariableAccess() { this = TClassVariableAccess(g, v) }
|
||||
|
||||
final override ClassVariable getVariable() { result = v }
|
||||
override ClassVariable getVariable() { none() }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "ClassVariableAccess" }
|
||||
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ private import TreeSitter
|
||||
private import codeql_ruby.ast.internal.Parameter
|
||||
private import codeql_ruby.ast.internal.Variable
|
||||
private import codeql_ruby.AST as AST
|
||||
private import Synthesis
|
||||
|
||||
module MethodName {
|
||||
predicate range(Generated::UnderscoreMethodName g) {
|
||||
@@ -16,11 +17,16 @@ module MethodName {
|
||||
@token_identifier or @token_instance_variable or @token_operator;
|
||||
}
|
||||
|
||||
private predicate mkSynthChild(SynthKind kind, AST::AstNode parent, int i) {
|
||||
any(Synthesis s).child(parent, i, SynthChild(kind))
|
||||
}
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
newtype TAstNode =
|
||||
TAddExpr(Generated::Binary g) { g instanceof @binary_plus } or
|
||||
TAddExprReal(Generated::Binary g) { g instanceof @binary_plus } or
|
||||
TAddExprSynth(AST::AstNode parent, int i) { mkSynthChild(AddExprKind(), parent, i) } or
|
||||
TAliasStmt(Generated::Alias g) or
|
||||
TArgumentList(Generated::AstNode g) {
|
||||
(
|
||||
@@ -50,7 +56,8 @@ private module Cached {
|
||||
TAssignExponentExpr(Generated::OperatorAssignment g) {
|
||||
g instanceof @operator_assignment_starstarequal
|
||||
} or
|
||||
TAssignExpr(Generated::Assignment g) or
|
||||
TAssignExprReal(Generated::Assignment g) or
|
||||
TAssignExprSynth(AST::AstNode parent, int i) { mkSynthChild(AssignExprKind(), parent, i) } or
|
||||
TAssignLShiftExpr(Generated::OperatorAssignment g) {
|
||||
g instanceof @operator_assignment_langlelangleequal
|
||||
} or
|
||||
@@ -72,9 +79,16 @@ private module Cached {
|
||||
TBareSymbolLiteral(Generated::BareSymbol g) or
|
||||
TBeginBlock(Generated::BeginBlock g) or
|
||||
TBeginExpr(Generated::Begin g) or
|
||||
TBitwiseAndExpr(Generated::Binary g) { g instanceof @binary_ampersand } or
|
||||
TBitwiseOrExpr(Generated::Binary g) { g instanceof @binary_pipe } or
|
||||
TBitwiseXorExpr(Generated::Binary g) { g instanceof @binary_caret } or
|
||||
TBitwiseAndExprReal(Generated::Binary g) { g instanceof @binary_ampersand } or
|
||||
TBitwiseAndExprSynth(AST::AstNode parent, int i) {
|
||||
mkSynthChild(BitwiseAndExprKind(), parent, i)
|
||||
} or
|
||||
TBitwiseOrExprReal(Generated::Binary g) { g instanceof @binary_pipe } or
|
||||
TBitwiseOrExprSynth(AST::AstNode parent, int i) { mkSynthChild(BitwiseOrExprKind(), parent, i) } or
|
||||
TBitwiseXorExprReal(Generated::Binary g) { g instanceof @binary_caret } or
|
||||
TBitwiseXorExprSynth(AST::AstNode parent, int i) {
|
||||
mkSynthChild(BitwiseXorExprKind(), parent, i)
|
||||
} or
|
||||
TBlockArgument(Generated::BlockArgument g) or
|
||||
TBlockParameter(Generated::BlockParameter g) or
|
||||
TBraceBlock(Generated::Block g) { not g.getParent() instanceof Generated::Lambda } or
|
||||
@@ -83,9 +97,12 @@ private module Cached {
|
||||
TCaseExpr(Generated::Case g) or
|
||||
TCharacterLiteral(Generated::Character g) or
|
||||
TClassDeclaration(Generated::Class g) or
|
||||
TClassVariableAccess(Generated::ClassVariable g, AST::ClassVariable v) {
|
||||
TClassVariableAccessReal(Generated::ClassVariable g, AST::ClassVariable v) {
|
||||
ClassVariableAccess::range(g, v)
|
||||
} or
|
||||
TClassVariableAccessSynth(AST::AstNode parent, int i, AST::ClassVariable v) {
|
||||
mkSynthChild(ClassVariableAccessKind(v), parent, i)
|
||||
} or
|
||||
TComplementExpr(Generated::Unary g) { g instanceof @unary_tilde } or
|
||||
TComplexLiteral(Generated::Complex g) or
|
||||
TDefinedExpr(Generated::Unary g) { g instanceof @unary_definedquestion } or
|
||||
@@ -93,7 +110,8 @@ private module Cached {
|
||||
TDestructuredLeftAssignment(Generated::DestructuredLeftAssignment g) {
|
||||
not strictcount(int i | exists(g.getParent().(Generated::LeftAssignmentList).getChild(i))) = 1
|
||||
} or
|
||||
TDivExpr(Generated::Binary g) { g instanceof @binary_slash } or
|
||||
TDivExprReal(Generated::Binary g) { g instanceof @binary_slash } or
|
||||
TDivExprSynth(AST::AstNode parent, int i) { mkSynthChild(DivExprKind(), parent, i) } or
|
||||
TDo(Generated::Do g) or
|
||||
TDoBlock(Generated::DoBlock g) { not g.getParent() instanceof Generated::Lambda } or
|
||||
TElementReference(Generated::ElementReference g) or
|
||||
@@ -103,17 +121,20 @@ private module Cached {
|
||||
TEndBlock(Generated::EndBlock g) or
|
||||
TEnsure(Generated::Ensure g) or
|
||||
TEqExpr(Generated::Binary g) { g instanceof @binary_equalequal } or
|
||||
TExplicitSelf(Generated::Self g) or
|
||||
TExponentExpr(Generated::Binary g) { g instanceof @binary_starstar } or
|
||||
TExponentExprReal(Generated::Binary g) { g instanceof @binary_starstar } or
|
||||
TExponentExprSynth(AST::AstNode parent, int i) { mkSynthChild(ExponentExprKind(), parent, i) } or
|
||||
TFalseLiteral(Generated::False g) or
|
||||
TFloatLiteral(Generated::Float g) { not any(Generated::Rational r).getChild() = g } or
|
||||
TForExpr(Generated::For g) or
|
||||
TForIn(Generated::In g) or // TODO REMOVE
|
||||
TGEExpr(Generated::Binary g) { g instanceof @binary_rangleequal } or
|
||||
TGTExpr(Generated::Binary g) { g instanceof @binary_rangle } or
|
||||
TGlobalVariableAccess(Generated::GlobalVariable g, AST::GlobalVariable v) {
|
||||
TGlobalVariableAccessReal(Generated::GlobalVariable g, AST::GlobalVariable v) {
|
||||
GlobalVariableAccess::range(g, v)
|
||||
} or
|
||||
TGlobalVariableAccessSynth(AST::AstNode parent, int i, AST::GlobalVariable v) {
|
||||
mkSynthChild(GlobalVariableAccessKind(v), parent, i)
|
||||
} or
|
||||
THashKeySymbolLiteral(Generated::HashKeySymbol g) or
|
||||
THashLiteral(Generated::Hash g) or
|
||||
THashSplatArgument(Generated::HashSplatArgument g) or
|
||||
@@ -122,34 +143,44 @@ private module Cached {
|
||||
TIdentifierMethodCall(Generated::Identifier g) { isIdentifierMethodCall(g) } or
|
||||
TIf(Generated::If g) or
|
||||
TIfModifierExpr(Generated::IfModifier g) or
|
||||
TImplicitSelf(Generated::AstNode g) {
|
||||
isIdentifierMethodCall(g)
|
||||
or
|
||||
isRegularMethodCall(g) and
|
||||
not exists(g.(Generated::Call).getReceiver()) and
|
||||
not exists(g.(Generated::Call).getMethod().(Generated::ScopeResolution).getScope())
|
||||
} or
|
||||
TInstanceVariableAccess(Generated::InstanceVariable g, AST::InstanceVariable v) {
|
||||
TInstanceVariableAccessReal(Generated::InstanceVariable g, AST::InstanceVariable v) {
|
||||
InstanceVariableAccess::range(g, v)
|
||||
} or
|
||||
TInstanceVariableAccessSynth(AST::AstNode parent, int i, AST::InstanceVariable v) {
|
||||
mkSynthChild(InstanceVariableAccessKind(v), parent, i)
|
||||
} or
|
||||
TIntegerLiteral(Generated::Integer g) { not any(Generated::Rational r).getChild() = g } or
|
||||
TKeywordParameter(Generated::KeywordParameter g) or
|
||||
TLEExpr(Generated::Binary g) { g instanceof @binary_langleequal } or
|
||||
TLShiftExpr(Generated::Binary g) { g instanceof @binary_langlelangle } or
|
||||
TLShiftExprReal(Generated::Binary g) { g instanceof @binary_langlelangle } or
|
||||
TLShiftExprSynth(AST::AstNode parent, int i) { mkSynthChild(LShiftExprKind(), parent, i) } or
|
||||
TLTExpr(Generated::Binary g) { g instanceof @binary_langle } or
|
||||
TLambda(Generated::Lambda g) or
|
||||
TLeftAssignmentList(Generated::LeftAssignmentList g) or
|
||||
TLocalVariableAccess(Generated::Identifier g, AST::LocalVariable v) {
|
||||
TLocalVariableAccessReal(Generated::Identifier g, AST::LocalVariable v) {
|
||||
LocalVariableAccess::range(g, v)
|
||||
} or
|
||||
TLogicalAndExpr(Generated::Binary g) {
|
||||
TLocalVariableAccessSynth(AST::AstNode parent, int i, AST::LocalVariable v) {
|
||||
mkSynthChild(LocalVariableAccessRealKind(v), parent, i)
|
||||
or
|
||||
mkSynthChild(LocalVariableAccessSynthKind(v), parent, i)
|
||||
} or
|
||||
TLogicalAndExprReal(Generated::Binary g) {
|
||||
g instanceof @binary_and or g instanceof @binary_ampersandampersand
|
||||
} or
|
||||
TLogicalOrExpr(Generated::Binary g) { g instanceof @binary_or or g instanceof @binary_pipepipe } or
|
||||
TLogicalAndExprSynth(AST::AstNode parent, int i) {
|
||||
mkSynthChild(LogicalAndExprKind(), parent, i)
|
||||
} or
|
||||
TLogicalOrExprReal(Generated::Binary g) {
|
||||
g instanceof @binary_or or g instanceof @binary_pipepipe
|
||||
} or
|
||||
TLogicalOrExprSynth(AST::AstNode parent, int i) { mkSynthChild(LogicalOrExprKind(), parent, i) } or
|
||||
TMethod(Generated::Method g) or
|
||||
TModuleDeclaration(Generated::Module g) or
|
||||
TModuloExpr(Generated::Binary g) { g instanceof @binary_percent } or
|
||||
TMulExpr(Generated::Binary g) { g instanceof @binary_star } or
|
||||
TModuloExprReal(Generated::Binary g) { g instanceof @binary_percent } or
|
||||
TModuloExprSynth(AST::AstNode parent, int i) { mkSynthChild(ModuloExprKind(), parent, i) } or
|
||||
TMulExprReal(Generated::Binary g) { g instanceof @binary_star } or
|
||||
TMulExprSynth(AST::AstNode parent, int i) { mkSynthChild(MulExprKind(), parent, i) } or
|
||||
TNEExpr(Generated::Binary g) { g instanceof @binary_bangequal } or
|
||||
TNextStmt(Generated::Next g) or
|
||||
TNilLiteral(Generated::Nil g) or
|
||||
@@ -158,7 +189,8 @@ private module Cached {
|
||||
TOptionalParameter(Generated::OptionalParameter g) or
|
||||
TPair(Generated::Pair g) or
|
||||
TParenthesizedExpr(Generated::ParenthesizedStatements g) or
|
||||
TRShiftExpr(Generated::Binary g) { g instanceof @binary_ranglerangle } or
|
||||
TRShiftExprReal(Generated::Binary g) { g instanceof @binary_ranglerangle } or
|
||||
TRShiftExprSynth(AST::AstNode parent, int i) { mkSynthChild(RShiftExprKind(), parent, i) } or
|
||||
TRangeLiteral(Generated::Range g) or
|
||||
TRationalLiteral(Generated::Rational g) or
|
||||
TRedoStmt(Generated::Redo g) or
|
||||
@@ -187,6 +219,8 @@ private module Cached {
|
||||
i = g.getName() and
|
||||
not exists(Generated::Call c | c.getMethod() = g)
|
||||
} or
|
||||
TSelfReal(Generated::Self g) or
|
||||
TSelfSynth(AST::AstNode parent, int i) { mkSynthChild(SelfKind(), parent, i) } or
|
||||
TSimpleParameter(Generated::Identifier g) { g instanceof Parameter::Range } or
|
||||
TSimpleSymbolLiteral(Generated::SimpleSymbol g) or
|
||||
TSingletonClass(Generated::SingletonClass g) or
|
||||
@@ -194,6 +228,7 @@ private module Cached {
|
||||
TSpaceshipExpr(Generated::Binary g) { g instanceof @binary_langleequalrangle } or
|
||||
TSplatArgument(Generated::SplatArgument g) or
|
||||
TSplatParameter(Generated::SplatParameter g) or
|
||||
TStmtSequenceSynth(AST::AstNode parent, int i) { mkSynthChild(StmtSequenceKind(), parent, i) } or
|
||||
TStringArrayLiteral(Generated::StringArray g) or
|
||||
TStringConcatenation(Generated::ChainedString g) or
|
||||
TStringEscapeSequenceComponent(Generated::EscapeSequence g) or
|
||||
@@ -201,7 +236,8 @@ private module Cached {
|
||||
TStringTextComponent(Generated::Token g) {
|
||||
g instanceof Generated::StringContent or g instanceof Generated::HeredocContent
|
||||
} or
|
||||
TSubExpr(Generated::Binary g) { g instanceof @binary_minus } or
|
||||
TSubExprReal(Generated::Binary g) { g instanceof @binary_minus } or
|
||||
TSubExprSynth(AST::AstNode parent, int i) { mkSynthChild(SubExprKind(), parent, i) } or
|
||||
TSubshellLiteral(Generated::Subshell g) or
|
||||
TSymbolArrayLiteral(Generated::SymbolArray g) or
|
||||
TTernaryIfExpr(Generated::Conditional g) or
|
||||
@@ -245,7 +281,7 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
Generated::AstNode toGenerated(AST::AstNode n) {
|
||||
n = TAddExpr(result) or
|
||||
n = TAddExprReal(result) or
|
||||
n = TAliasStmt(result) or
|
||||
n = TArgumentList(result) or
|
||||
n = TAssignAddExpr(result) or
|
||||
@@ -254,7 +290,7 @@ private module Cached {
|
||||
n = TAssignBitwiseXorExpr(result) or
|
||||
n = TAssignDivExpr(result) or
|
||||
n = TAssignExponentExpr(result) or
|
||||
n = TAssignExpr(result) or
|
||||
n = TAssignExprReal(result) or
|
||||
n = TAssignLShiftExpr(result) or
|
||||
n = TAssignLogicalAndExpr(result) or
|
||||
n = TAssignLogicalOrExpr(result) or
|
||||
@@ -266,9 +302,9 @@ private module Cached {
|
||||
n = TBareSymbolLiteral(result) or
|
||||
n = TBeginBlock(result) or
|
||||
n = TBeginExpr(result) or
|
||||
n = TBitwiseAndExpr(result) or
|
||||
n = TBitwiseOrExpr(result) or
|
||||
n = TBitwiseXorExpr(result) or
|
||||
n = TBitwiseAndExprReal(result) or
|
||||
n = TBitwiseOrExprReal(result) or
|
||||
n = TBitwiseXorExprReal(result) or
|
||||
n = TBlockArgument(result) or
|
||||
n = TBlockParameter(result) or
|
||||
n = TBraceBlock(result) or
|
||||
@@ -277,13 +313,13 @@ private module Cached {
|
||||
n = TCaseExpr(result) or
|
||||
n = TCharacterLiteral(result) or
|
||||
n = TClassDeclaration(result) or
|
||||
n = TClassVariableAccess(result, _) or
|
||||
n = TClassVariableAccessReal(result, _) or
|
||||
n = TComplementExpr(result) or
|
||||
n = TComplexLiteral(result) or
|
||||
n = TDefinedExpr(result) or
|
||||
n = TDelimitedSymbolLiteral(result) or
|
||||
n = TDestructuredLeftAssignment(result) or
|
||||
n = TDivExpr(result) or
|
||||
n = TDivExprReal(result) or
|
||||
n = TDo(result) or
|
||||
n = TDoBlock(result) or
|
||||
n = TElementReference(result) or
|
||||
@@ -293,15 +329,14 @@ private module Cached {
|
||||
n = TEndBlock(result) or
|
||||
n = TEnsure(result) or
|
||||
n = TEqExpr(result) or
|
||||
n = TExplicitSelf(result) or
|
||||
n = TExponentExpr(result) or
|
||||
n = TExponentExprReal(result) or
|
||||
n = TFalseLiteral(result) or
|
||||
n = TFloatLiteral(result) or
|
||||
n = TForExpr(result) or
|
||||
n = TForIn(result) or // TODO REMOVE
|
||||
n = TGEExpr(result) or
|
||||
n = TGTExpr(result) or
|
||||
n = TGlobalVariableAccess(result, _) or
|
||||
n = TGlobalVariableAccessReal(result, _) or
|
||||
n = THashKeySymbolLiteral(result) or
|
||||
n = THashLiteral(result) or
|
||||
n = THashSplatArgument(result) or
|
||||
@@ -310,21 +345,21 @@ private module Cached {
|
||||
n = TIdentifierMethodCall(result) or
|
||||
n = TIf(result) or
|
||||
n = TIfModifierExpr(result) or
|
||||
n = TInstanceVariableAccess(result, _) or
|
||||
n = TInstanceVariableAccessReal(result, _) or
|
||||
n = TIntegerLiteral(result) or
|
||||
n = TKeywordParameter(result) or
|
||||
n = TLEExpr(result) or
|
||||
n = TLShiftExpr(result) or
|
||||
n = TLShiftExprReal(result) or
|
||||
n = TLTExpr(result) or
|
||||
n = TLambda(result) or
|
||||
n = TLeftAssignmentList(result) or
|
||||
n = TLocalVariableAccess(result, _) or
|
||||
n = TLogicalAndExpr(result) or
|
||||
n = TLogicalOrExpr(result) or
|
||||
n = TLocalVariableAccessReal(result, _) or
|
||||
n = TLogicalAndExprReal(result) or
|
||||
n = TLogicalOrExprReal(result) or
|
||||
n = TMethod(result) or
|
||||
n = TModuleDeclaration(result) or
|
||||
n = TModuloExpr(result) or
|
||||
n = TMulExpr(result) or
|
||||
n = TModuloExprReal(result) or
|
||||
n = TMulExprReal(result) or
|
||||
n = TNEExpr(result) or
|
||||
n = TNextStmt(result) or
|
||||
n = TNilLiteral(result) or
|
||||
@@ -333,7 +368,7 @@ private module Cached {
|
||||
n = TOptionalParameter(result) or
|
||||
n = TPair(result) or
|
||||
n = TParenthesizedExpr(result) or
|
||||
n = TRShiftExpr(result) or
|
||||
n = TRShiftExprReal(result) or
|
||||
n = TRangeLiteral(result) or
|
||||
n = TRationalLiteral(result) or
|
||||
n = TRedoStmt(result) or
|
||||
@@ -349,6 +384,7 @@ private module Cached {
|
||||
n = TReturnStmt(result) or
|
||||
n = TScopeResolutionConstantAccess(result, _) or
|
||||
n = TScopeResolutionMethodCall(result, _) or
|
||||
n = TSelfReal(result) or
|
||||
n = TSimpleParameter(result) or
|
||||
n = TSimpleSymbolLiteral(result) or
|
||||
n = TSingletonClass(result) or
|
||||
@@ -361,7 +397,7 @@ private module Cached {
|
||||
n = TStringEscapeSequenceComponent(result) or
|
||||
n = TStringInterpolationComponent(result) or
|
||||
n = TStringTextComponent(result) or
|
||||
n = TSubExpr(result) or
|
||||
n = TSubExprReal(result) or
|
||||
n = TSubshellLiteral(result) or
|
||||
n = TSymbolArrayLiteral(result) or
|
||||
n = TTernaryIfExpr(result) or
|
||||
@@ -385,14 +421,105 @@ private module Cached {
|
||||
n = TYieldCall(result)
|
||||
}
|
||||
|
||||
/** Gets the `i`th synthesized child of `parent`. */
|
||||
cached
|
||||
AST::AstNode getSynthChild(AST::AstNode parent, int i) {
|
||||
exists(SynthKind kind | mkSynthChild(kind, parent, i) |
|
||||
kind = AddExprKind() and
|
||||
result = TAddExprSynth(parent, i)
|
||||
or
|
||||
kind = AssignExprKind() and
|
||||
result = TAssignExprSynth(parent, i)
|
||||
or
|
||||
kind = BitwiseAndExprKind() and
|
||||
result = TBitwiseAndExprSynth(parent, i)
|
||||
or
|
||||
kind = BitwiseOrExprKind() and
|
||||
result = TBitwiseOrExprSynth(parent, i)
|
||||
or
|
||||
kind = BitwiseXorExprKind() and
|
||||
result = TBitwiseXorExprSynth(parent, i)
|
||||
or
|
||||
exists(AST::ClassVariable v |
|
||||
kind = ClassVariableAccessKind(v) and
|
||||
result = TClassVariableAccessSynth(parent, i, v)
|
||||
)
|
||||
or
|
||||
kind = DivExprKind() and
|
||||
result = TDivExprSynth(parent, i)
|
||||
or
|
||||
kind = ExponentExprKind() and
|
||||
result = TExponentExprSynth(parent, i)
|
||||
or
|
||||
exists(AST::GlobalVariable v |
|
||||
kind = GlobalVariableAccessKind(v) and
|
||||
result = TGlobalVariableAccessSynth(parent, i, v)
|
||||
)
|
||||
or
|
||||
exists(AST::InstanceVariable v |
|
||||
kind = InstanceVariableAccessKind(v) and
|
||||
result = TInstanceVariableAccessSynth(parent, i, v)
|
||||
)
|
||||
or
|
||||
kind = LShiftExprKind() and
|
||||
result = TLShiftExprSynth(parent, i)
|
||||
or
|
||||
exists(AST::LocalVariable v | result = TLocalVariableAccessSynth(parent, i, v) |
|
||||
kind = LocalVariableAccessRealKind(v)
|
||||
or
|
||||
kind = LocalVariableAccessSynthKind(v)
|
||||
)
|
||||
or
|
||||
kind = LogicalAndExprKind() and
|
||||
result = TLogicalAndExprSynth(parent, i)
|
||||
or
|
||||
kind = LogicalOrExprKind() and
|
||||
result = TLogicalOrExprSynth(parent, i)
|
||||
or
|
||||
kind = ModuloExprKind() and
|
||||
result = TModuloExprSynth(parent, i)
|
||||
or
|
||||
kind = MulExprKind() and
|
||||
result = TMulExprSynth(parent, i)
|
||||
or
|
||||
kind = RShiftExprKind() and
|
||||
result = TRShiftExprSynth(parent, i)
|
||||
or
|
||||
kind = SelfKind() and
|
||||
result = TSelfSynth(parent, i)
|
||||
or
|
||||
kind = StmtSequenceKind() and
|
||||
result = TStmtSequenceSynth(parent, i)
|
||||
or
|
||||
kind = SubExprKind() and
|
||||
result = TSubExprSynth(parent, i)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `i`th child of `parent` is `child`. Either `parent` or
|
||||
* `child` (or both) is a synthesized node.
|
||||
*/
|
||||
cached
|
||||
predicate synthChild(AST::AstNode parent, int i, AST::AstNode child) {
|
||||
child = getSynthChild(parent, i)
|
||||
or
|
||||
any(Synthesis s).child(parent, i, RealChild(child))
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `toGenerated`, but also returns generated nodes for synthesized AST
|
||||
* nodes.
|
||||
*/
|
||||
cached
|
||||
Generated::AstNode toGeneratedInclSynth(AST::AstNode n) {
|
||||
result = toGenerated(n) or
|
||||
n = TImplicitSelf(result)
|
||||
result = toGenerated(n)
|
||||
or
|
||||
not exists(toGenerated(n)) and
|
||||
exists(AST::AstNode parent |
|
||||
synthChild(parent, _, n) and
|
||||
result = toGeneratedInclSynth(parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +548,7 @@ class TConditionalLoop = TWhileExpr or TUntilExpr or TWhileModifierExpr or TUnti
|
||||
|
||||
class TLoop = TConditionalLoop or TForExpr;
|
||||
|
||||
class TSelf = TExplicitSelf or TImplicitSelf;
|
||||
class TSelf = TSelfReal or TSelfSynth;
|
||||
|
||||
class TExpr =
|
||||
TSelf or TArgumentList or TRescueClause or TRescueModifierExpr or TPair or TStringConcatenation or
|
||||
@@ -431,7 +558,7 @@ class TExpr =
|
||||
|
||||
class TStmtSequence =
|
||||
TBeginBlock or TEndBlock or TThen or TElse or TDo or TEnsure or TStringInterpolationComponent or
|
||||
TBlock or TBodyStmt or TParenthesizedExpr;
|
||||
TBlock or TBodyStmt or TParenthesizedExpr or TStmtSequenceSynth;
|
||||
|
||||
class TBodyStmt = TBeginExpr or TModuleBase or TMethod or TLambda or TDoBlock or TSingletonMethod;
|
||||
|
||||
@@ -485,17 +612,45 @@ class TBinaryOperation =
|
||||
class TBinaryArithmeticOperation =
|
||||
TAddExpr or TSubExpr or TMulExpr or TDivExpr or TModuloExpr or TExponentExpr;
|
||||
|
||||
class TAddExpr = TAddExprReal or TAddExprSynth;
|
||||
|
||||
class TSubExpr = TSubExprReal or TSubExprSynth;
|
||||
|
||||
class TMulExpr = TMulExprReal or TMulExprSynth;
|
||||
|
||||
class TDivExpr = TDivExprReal or TDivExprSynth;
|
||||
|
||||
class TModuloExpr = TModuloExprReal or TModuloExprSynth;
|
||||
|
||||
class TExponentExpr = TExponentExprReal or TExponentExprSynth;
|
||||
|
||||
class TBinaryLogicalOperation = TLogicalAndExpr or TLogicalOrExpr;
|
||||
|
||||
class TLogicalAndExpr = TLogicalAndExprReal or TLogicalAndExprSynth;
|
||||
|
||||
class TLogicalOrExpr = TLogicalOrExprReal or TLogicalOrExprSynth;
|
||||
|
||||
class TBinaryBitwiseOperation =
|
||||
TLShiftExpr or TRShiftExpr or TBitwiseAndExpr or TBitwiseOrExpr or TBitwiseXorExpr;
|
||||
|
||||
class TLShiftExpr = TLShiftExprReal or TLShiftExprSynth;
|
||||
|
||||
class TRShiftExpr = TRShiftExprReal or TRShiftExprSynth;
|
||||
|
||||
class TBitwiseAndExpr = TBitwiseAndExprReal or TBitwiseAndExprSynth;
|
||||
|
||||
class TBitwiseOrExpr = TBitwiseOrExprReal or TBitwiseOrExprSynth;
|
||||
|
||||
class TBitwiseXorExpr = TBitwiseXorExprReal or TBitwiseXorExprSynth;
|
||||
|
||||
class TComparisonOperation = TEqualityOperation or TRelationalOperation;
|
||||
|
||||
class TEqualityOperation = TEqExpr or TNEExpr or TCaseEqExpr;
|
||||
|
||||
class TRelationalOperation = TGTExpr or TGEExpr or TLTExpr or TLEExpr;
|
||||
|
||||
class TAssignExpr = TAssignExprReal or TAssignExprSynth;
|
||||
|
||||
class TAssignment = TAssignExpr or TAssignOperation;
|
||||
|
||||
class TAssignOperation =
|
||||
@@ -531,3 +686,11 @@ class TTuplePattern = TTuplePatternParameter or TDestructuredLeftAssignment or T
|
||||
|
||||
class TVariableAccess =
|
||||
TLocalVariableAccess or TGlobalVariableAccess or TInstanceVariableAccess or TClassVariableAccess;
|
||||
|
||||
class TLocalVariableAccess = TLocalVariableAccessReal or TLocalVariableAccessSynth;
|
||||
|
||||
class TGlobalVariableAccess = TGlobalVariableAccessReal or TGlobalVariableAccessSynth;
|
||||
|
||||
class TInstanceVariableAccess = TInstanceVariableAccessReal or TInstanceVariableAccessSynth;
|
||||
|
||||
class TClassVariableAccess = TClassVariableAccessReal or TClassVariableAccessSynth;
|
||||
|
||||
@@ -67,7 +67,7 @@ private module Cached {
|
||||
m = resolveScopeExpr(c.getReceiver())
|
||||
or
|
||||
m = enclosingModule(c).getModule() and
|
||||
c.receiverIsSelf()
|
||||
c.getReceiver() instanceof Self
|
||||
) and
|
||||
result = resolveScopeExpr(c.getAnArgument())
|
||||
)
|
||||
@@ -81,7 +81,7 @@ private module Cached {
|
||||
m = resolveScopeExpr(c.getReceiver())
|
||||
or
|
||||
m = enclosingModule(c).getModule() and
|
||||
c.receiverIsSelf()
|
||||
c.getReceiver() instanceof Self
|
||||
) and
|
||||
result = resolveScopeExpr(c.getAnArgument())
|
||||
)
|
||||
|
||||
173
ql/src/codeql_ruby/ast/internal/Synthesis.qll
Normal file
173
ql/src/codeql_ruby/ast/internal/Synthesis.qll
Normal file
@@ -0,0 +1,173 @@
|
||||
/** Provides predicates for synthesizing AST nodes. */
|
||||
|
||||
private import AST
|
||||
private import TreeSitter
|
||||
private import codeql_ruby.ast.internal.Parameter
|
||||
private import codeql_ruby.ast.internal.Variable
|
||||
private import codeql_ruby.AST
|
||||
|
||||
/** A synthesized AST node kind. */
|
||||
newtype SynthKind =
|
||||
AddExprKind() or
|
||||
AssignExprKind() or
|
||||
BitwiseAndExprKind() or
|
||||
BitwiseOrExprKind() or
|
||||
BitwiseXorExprKind() or
|
||||
ClassVariableAccessKind(ClassVariable v) or
|
||||
DivExprKind() or
|
||||
ExponentExprKind() or
|
||||
GlobalVariableAccessKind(GlobalVariable v) or
|
||||
InstanceVariableAccessKind(InstanceVariable v) or
|
||||
LShiftExprKind() or
|
||||
LocalVariableAccessRealKind(LocalVariableReal v) or
|
||||
LocalVariableAccessSynthKind(TLocalVariableSynth v) or
|
||||
LogicalAndExprKind() or
|
||||
LogicalOrExprKind() or
|
||||
ModuloExprKind() or
|
||||
MulExprKind() or
|
||||
StmtSequenceKind() or
|
||||
RShiftExprKind() or
|
||||
SelfKind() or
|
||||
SubExprKind()
|
||||
|
||||
/**
|
||||
* An AST child.
|
||||
*
|
||||
* Either a new synthesized node or a reference to an existing node.
|
||||
*/
|
||||
newtype Child =
|
||||
SynthChild(SynthKind k) or
|
||||
RealChild(AstNode n)
|
||||
|
||||
private newtype TSynthesis = MkSynthesis()
|
||||
|
||||
/** A class used for synthesizing AST nodes. */
|
||||
class Synthesis extends TSynthesis {
|
||||
/**
|
||||
* Holds if a node should be synthesized as the `i`th child of `parent`, or if
|
||||
* a non-synthesized node should be the `i`th child of synthesized node `parent`.
|
||||
*
|
||||
* `i = -1` is used to represent that the synthesized node is a desugared version
|
||||
* of its parent.
|
||||
*/
|
||||
predicate child(AstNode parent, int i, Child child) { none() }
|
||||
|
||||
/**
|
||||
* Holds if a local variable, identified by `i`, should be synthesized for AST
|
||||
* node `n`.
|
||||
*/
|
||||
predicate localVariable(AstNode n, int i) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `n` should be excluded from `ControlFlowTree` in the CFG construction.
|
||||
*/
|
||||
predicate excludeFromControlFlowTree(AstNode n) { none() }
|
||||
|
||||
final string toString() { none() }
|
||||
}
|
||||
|
||||
private module ImplicitSelfSynthesis {
|
||||
private class IdentifierMethodCallSelfSynthesis extends Synthesis {
|
||||
final override predicate child(AstNode parent, int i, Child child) {
|
||||
child = SynthChild(SelfKind()) and
|
||||
parent = TIdentifierMethodCall(_) and
|
||||
i = 0
|
||||
}
|
||||
}
|
||||
|
||||
private class RegularMethodCallSelfSynthesis extends Synthesis {
|
||||
final override predicate child(AstNode parent, int i, Child child) {
|
||||
child = SynthChild(SelfKind()) and
|
||||
i = 0 and
|
||||
exists(Generated::AstNode g |
|
||||
parent = TRegularMethodCall(g) and
|
||||
// If there's no explicit receiver (or scope resolution that acts like a
|
||||
// receiver), then the receiver is implicitly `self`. N.B. `::Foo()` is
|
||||
// not valid Ruby.
|
||||
not exists(g.(Generated::Call).getReceiver()) and
|
||||
not exists(g.(Generated::Call).getMethod().(Generated::ScopeResolution).getScope())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private module AssignOperationDesugar {
|
||||
/**
|
||||
* Gets the operator kind to synthesize for operator assignment `ao`.
|
||||
*/
|
||||
private SynthKind getKind(AssignOperation ao) {
|
||||
ao instanceof AssignAddExpr and result = AddExprKind()
|
||||
or
|
||||
ao instanceof AssignSubExpr and result = SubExprKind()
|
||||
or
|
||||
ao instanceof AssignMulExpr and result = MulExprKind()
|
||||
or
|
||||
ao instanceof AssignDivExpr and result = DivExprKind()
|
||||
or
|
||||
ao instanceof AssignModuloExpr and result = ModuloExprKind()
|
||||
or
|
||||
ao instanceof AssignExponentExpr and result = ExponentExprKind()
|
||||
or
|
||||
ao instanceof AssignLogicalAndExpr and result = LogicalAndExprKind()
|
||||
or
|
||||
ao instanceof AssignLogicalOrExpr and result = LogicalOrExprKind()
|
||||
or
|
||||
ao instanceof AssignLShiftExpr and result = LShiftExprKind()
|
||||
or
|
||||
ao instanceof AssignRShiftExpr and result = RShiftExprKind()
|
||||
or
|
||||
ao instanceof AssignBitwiseAndExpr and result = BitwiseAndExprKind()
|
||||
or
|
||||
ao instanceof AssignBitwiseOrExpr and result = BitwiseOrExprKind()
|
||||
or
|
||||
ao instanceof AssignBitwiseXorExpr and result = BitwiseXorExprKind()
|
||||
}
|
||||
|
||||
/**
|
||||
* ```rb
|
||||
* x += y
|
||||
* ```
|
||||
*
|
||||
* desguars to
|
||||
*
|
||||
* ```rb
|
||||
* x = x + y
|
||||
* ```
|
||||
*
|
||||
* when `x` is a variable.
|
||||
*/
|
||||
private class VariableAssignOperationSynthesis extends Synthesis {
|
||||
final override predicate child(AstNode parent, int i, Child child) {
|
||||
exists(AssignOperation ao, VariableReal v |
|
||||
v = ao.getLeftOperand().(VariableAccessReal).getVariableReal()
|
||||
|
|
||||
parent = ao and
|
||||
i = -1 and
|
||||
child = SynthChild(AssignExprKind())
|
||||
or
|
||||
exists(AstNode assign | assign = getSynthChild(ao, -1) |
|
||||
parent = assign and
|
||||
i = 0 and
|
||||
child = RealChild(ao.getLeftOperand())
|
||||
or
|
||||
parent = assign and
|
||||
i = 1 and
|
||||
child = SynthChild(getKind(ao))
|
||||
or
|
||||
parent = getSynthChild(assign, 1) and
|
||||
(
|
||||
i = 0 and
|
||||
child =
|
||||
SynthChild([
|
||||
LocalVariableAccessRealKind(v).(SynthKind), InstanceVariableAccessKind(v),
|
||||
ClassVariableAccessKind(v), GlobalVariableAccessKind(v)
|
||||
])
|
||||
or
|
||||
i = 1 and
|
||||
child = RealChild(ao.getRightOperand())
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ private import codeql_ruby.AST
|
||||
private import codeql_ruby.ast.internal.AST
|
||||
private import codeql_ruby.ast.internal.Parameter
|
||||
private import codeql_ruby.ast.internal.Scope
|
||||
private import codeql_ruby.ast.internal.Synthesis
|
||||
|
||||
/**
|
||||
* Holds if `n` is in the left-hand-side of an explicit assignment `assignment`.
|
||||
@@ -128,7 +129,7 @@ private module Cached {
|
||||
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
|
||||
)
|
||||
} or
|
||||
TLocalVariable(Scope::Range scope, string name, Generated::Identifier i) {
|
||||
TLocalVariableReal(Scope::Range scope, string name, Generated::Identifier i) {
|
||||
scopeDefinesParameterVariable(scope, name, i)
|
||||
or
|
||||
i =
|
||||
@@ -139,7 +140,8 @@ private module Cached {
|
||||
) and
|
||||
not scopeDefinesParameterVariable(scope, name, _) and
|
||||
not inherits(scope, name, _)
|
||||
}
|
||||
} or
|
||||
TLocalVariableSynth(AstNode n, int i) { any(Synthesis s).localVariable(n, i) }
|
||||
|
||||
// Db types that can be vcalls
|
||||
private class VcallToken =
|
||||
@@ -288,7 +290,7 @@ private module Cached {
|
||||
}
|
||||
|
||||
cached
|
||||
predicate access(Generated::Identifier access, Variable::Range variable) {
|
||||
predicate access(Generated::Identifier access, VariableReal::Range variable) {
|
||||
exists(string name |
|
||||
variable.getName() = name and
|
||||
name = access.getValue()
|
||||
@@ -372,8 +374,10 @@ private predicate inherits(Scope::Range scope, string name, Scope::Range outer)
|
||||
)
|
||||
}
|
||||
|
||||
module Variable {
|
||||
class Range extends TVariable {
|
||||
class TVariableReal = TGlobalVariable or TClassVariable or TInstanceVariable or TLocalVariableReal;
|
||||
|
||||
module VariableReal {
|
||||
class Range extends TVariableReal {
|
||||
abstract string getName();
|
||||
|
||||
string toString() { result = this.getName() }
|
||||
@@ -384,13 +388,15 @@ module Variable {
|
||||
}
|
||||
}
|
||||
|
||||
class TLocalVariable = TLocalVariableReal or TLocalVariableSynth;
|
||||
|
||||
module LocalVariable {
|
||||
class Range extends Variable::Range, TLocalVariable {
|
||||
class Range extends VariableReal::Range, TLocalVariableReal {
|
||||
private Scope::Range scope;
|
||||
private string name;
|
||||
private Generated::Identifier i;
|
||||
|
||||
Range() { this = TLocalVariable(scope, name, i) }
|
||||
Range() { this = TLocalVariableReal(scope, name, i) }
|
||||
|
||||
final override string getName() { result = name }
|
||||
|
||||
@@ -402,8 +408,41 @@ module LocalVariable {
|
||||
}
|
||||
}
|
||||
|
||||
class VariableReal extends Variable, TVariableReal {
|
||||
VariableReal::Range range;
|
||||
|
||||
VariableReal() { range = this }
|
||||
|
||||
final override string getName() { result = range.getName() }
|
||||
|
||||
final override Location getLocation() { result = range.getLocation() }
|
||||
|
||||
final override Scope getDeclaringScope() { toGenerated(result) = range.getDeclaringScope() }
|
||||
}
|
||||
|
||||
class LocalVariableReal extends VariableReal, LocalVariable, TLocalVariableReal {
|
||||
override LocalVariable::Range range;
|
||||
|
||||
final override LocalVariableAccessReal getAnAccess() { result.getVariable() = this }
|
||||
|
||||
final override VariableAccess getDefiningAccess() { result = range.getDefiningAccess() }
|
||||
}
|
||||
|
||||
class LocalVariableSynth extends LocalVariable, TLocalVariableSynth {
|
||||
private AstNode n;
|
||||
private int i;
|
||||
|
||||
LocalVariableSynth() { this = TLocalVariableSynth(n, i) }
|
||||
|
||||
final override string getName() { result = "__synth__" + i }
|
||||
|
||||
final override Location getLocation() { result = n.getLocation() }
|
||||
|
||||
final override Scope getDeclaringScope() { none() } // not relevant for synthesized variables
|
||||
}
|
||||
|
||||
module GlobalVariable {
|
||||
class Range extends Variable::Range, TGlobalVariable {
|
||||
class Range extends VariableReal::Range, TGlobalVariable {
|
||||
private string name;
|
||||
|
||||
Range() { this = TGlobalVariable(name) }
|
||||
@@ -417,7 +456,7 @@ module GlobalVariable {
|
||||
}
|
||||
|
||||
module InstanceVariable {
|
||||
class Range extends Variable::Range, TInstanceVariable {
|
||||
class Range extends VariableReal::Range, TInstanceVariable {
|
||||
private ModuleBase::Range scope;
|
||||
private boolean instance;
|
||||
private string name;
|
||||
@@ -436,7 +475,7 @@ module InstanceVariable {
|
||||
}
|
||||
|
||||
module ClassVariable {
|
||||
class Range extends Variable::Range, TClassVariable {
|
||||
class Range extends VariableReal::Range, TClassVariable {
|
||||
private ModuleBase::Range scope;
|
||||
private string name;
|
||||
private Generated::AstNode decl;
|
||||
@@ -464,16 +503,126 @@ module LocalVariableAccess {
|
||||
}
|
||||
}
|
||||
|
||||
class TVariableAccessReal =
|
||||
TLocalVariableAccessReal or TGlobalVariableAccess or TInstanceVariableAccess or
|
||||
TClassVariableAccess;
|
||||
|
||||
abstract class VariableAccessReal extends VariableAccess, TVariableAccessReal {
|
||||
/**
|
||||
* Same as `getVariable()`, but restricted to non-synthesized variable accesses.
|
||||
*
|
||||
* The sole purpose of this predicate is to make AST synthesis monotonic.
|
||||
*/
|
||||
abstract VariableReal getVariableReal();
|
||||
}
|
||||
|
||||
private class LocalVariableAccessReal extends VariableAccessReal, LocalVariableAccess,
|
||||
TLocalVariableAccessReal {
|
||||
private Generated::Identifier g;
|
||||
private LocalVariable v;
|
||||
|
||||
LocalVariableAccessReal() { this = TLocalVariableAccessReal(g, v) }
|
||||
|
||||
final override LocalVariable getVariable() { result = v }
|
||||
|
||||
final override LocalVariableReal getVariableReal() { result = v }
|
||||
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
private class LocalVariableAccessSynth extends LocalVariableAccess, TLocalVariableAccessSynth {
|
||||
private LocalVariable v;
|
||||
|
||||
LocalVariableAccessSynth() { this = TLocalVariableAccessSynth(_, _, v) }
|
||||
|
||||
final override LocalVariable getVariable() { result = v }
|
||||
|
||||
final override string toString() { result = v.getName() }
|
||||
}
|
||||
|
||||
module GlobalVariableAccess {
|
||||
predicate range(Generated::GlobalVariable n, GlobalVariable v) { n.getValue() = v.getName() }
|
||||
}
|
||||
|
||||
private class GlobalVariableAccessReal extends VariableAccessReal, GlobalVariableAccess,
|
||||
TGlobalVariableAccessReal {
|
||||
private Generated::GlobalVariable g;
|
||||
private GlobalVariable v;
|
||||
|
||||
GlobalVariableAccessReal() { this = TGlobalVariableAccessReal(g, v) }
|
||||
|
||||
final override GlobalVariable getVariable() { result = v }
|
||||
|
||||
final override GlobalVariable getVariableReal() { result = v }
|
||||
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
private class GlobalVariableAccessSynth extends GlobalVariableAccess, TGlobalVariableAccessSynth {
|
||||
private GlobalVariable v;
|
||||
|
||||
GlobalVariableAccessSynth() { this = TGlobalVariableAccessSynth(_, _, v) }
|
||||
|
||||
final override GlobalVariable getVariable() { result = v }
|
||||
|
||||
final override string toString() { result = v.getName() }
|
||||
}
|
||||
|
||||
module InstanceVariableAccess {
|
||||
predicate range(Generated::InstanceVariable n, InstanceVariable v) {
|
||||
instanceVariableAccess(n, v)
|
||||
}
|
||||
}
|
||||
|
||||
private class InstanceVariableAccessReal extends VariableAccessReal, InstanceVariableAccess,
|
||||
TInstanceVariableAccessReal {
|
||||
private Generated::InstanceVariable g;
|
||||
private InstanceVariable v;
|
||||
|
||||
InstanceVariableAccessReal() { this = TInstanceVariableAccessReal(g, v) }
|
||||
|
||||
final override InstanceVariable getVariable() { result = v }
|
||||
|
||||
final override InstanceVariable getVariableReal() { result = v }
|
||||
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
private class InstanceVariableAccessSynth extends InstanceVariableAccess,
|
||||
TInstanceVariableAccessSynth {
|
||||
private InstanceVariable v;
|
||||
|
||||
InstanceVariableAccessSynth() { this = TInstanceVariableAccessSynth(_, _, v) }
|
||||
|
||||
final override InstanceVariable getVariable() { result = v }
|
||||
|
||||
final override string toString() { result = v.getName() }
|
||||
}
|
||||
|
||||
module ClassVariableAccess {
|
||||
predicate range(Generated::ClassVariable n, ClassVariable v) { classVariableAccess(n, v) }
|
||||
}
|
||||
|
||||
private class ClassVariableAccessReal extends VariableAccessReal, ClassVariableAccess,
|
||||
TClassVariableAccessReal {
|
||||
private Generated::ClassVariable g;
|
||||
private ClassVariable v;
|
||||
|
||||
ClassVariableAccessReal() { this = TClassVariableAccessReal(g, v) }
|
||||
|
||||
final override ClassVariable getVariable() { result = v }
|
||||
|
||||
final override ClassVariable getVariableReal() { result = v }
|
||||
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
private class ClassVariableAccessSynth extends ClassVariableAccess, TClassVariableAccessSynth {
|
||||
private ClassVariable v;
|
||||
|
||||
ClassVariableAccessSynth() { this = TClassVariableAccessSynth(_, _, v) }
|
||||
|
||||
final override ClassVariable getVariable() { result = v }
|
||||
|
||||
final override string toString() { result = v.getName() }
|
||||
}
|
||||
|
||||
@@ -159,6 +159,13 @@ abstract private class ExprChildMapping extends Expr {
|
||||
)
|
||||
}
|
||||
|
||||
private Expr desugar(Expr n) {
|
||||
result = n.getDesugared()
|
||||
or
|
||||
not exists(n.getDesugared()) and
|
||||
result = n
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a control-flow path from `cfn` to `cfnChild`, where `cfn`
|
||||
* is a control-flow node for this expression, and `cfnChild` is a control-flow
|
||||
@@ -171,13 +178,13 @@ abstract private class ExprChildMapping extends Expr {
|
||||
exists(BasicBlock bb |
|
||||
this.reachesBasicBlockBase(child, cfn, bb) and
|
||||
cfnChild = bb.getANode() and
|
||||
cfnChild = child.getAControlFlowNode()
|
||||
cfnChild = desugar(child).getAControlFlowNode()
|
||||
)
|
||||
or
|
||||
exists(BasicBlock bb |
|
||||
this.reachesBasicBlockRec(child, cfn, bb) and
|
||||
cfnChild = bb.getANode() and
|
||||
cfnChild = child.getAControlFlowNode()
|
||||
cfnChild = desugar(child).getAControlFlowNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -247,6 +254,11 @@ module ExprNodes {
|
||||
override predicate relevantChild(Expr e) { e = this.getValue() or e = this.getBranch(_) }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `MethodCall` AST expression. */
|
||||
class MethodCallCfgNode extends CallCfgNode {
|
||||
MethodCallCfgNode() { this.getExpr() instanceof MethodCall }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `CaseExpr` AST expression. */
|
||||
class CaseExprCfgNode extends ExprCfgNode {
|
||||
override CaseExprChildMapping e;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @kind graph
|
||||
* @id rb/test/cfg
|
||||
*/
|
||||
|
||||
import codeql_ruby.CFG
|
||||
|
||||
query predicate nodes(CfgNode n, string attr, string val) {
|
||||
attr = "semmle.order" and
|
||||
val =
|
||||
any(int i |
|
||||
n =
|
||||
rank[i](CfgNode p |
|
||||
|
|
||||
p
|
||||
order by
|
||||
p.getLocation().getFile().getBaseName(), p.getLocation().getFile().getAbsolutePath(),
|
||||
p.getLocation().getStartLine(), p.getLocation().getStartColumn()
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
|
||||
query predicate edges(CfgNode pred, CfgNode succ, string attr, string val) {
|
||||
exists(SuccessorType t | succ = pred.getASuccessor(t) |
|
||||
attr = "semmle.label" and
|
||||
if t instanceof SuccessorTypes::NormalSuccessor then val = "" else val = t.toString()
|
||||
)
|
||||
}
|
||||
32
ql/src/codeql_ruby/controlflow/internal/Cfg.qll
Normal file
32
ql/src/codeql_ruby/controlflow/internal/Cfg.qll
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Import this module into a `.ql` file of `@kind graph` to render a CFG. The
|
||||
* graph is restricted to nodes from `RelevantCfgNode`.
|
||||
*/
|
||||
|
||||
private import codeql.Locations
|
||||
import codeql_ruby.CFG
|
||||
|
||||
abstract class RelevantCfgNode extends CfgNode { }
|
||||
|
||||
query predicate nodes(RelevantCfgNode n, string attr, string val) {
|
||||
attr = "semmle.order" and
|
||||
val =
|
||||
any(int i |
|
||||
n =
|
||||
rank[i](RelevantCfgNode p, Location l |
|
||||
l = p.getLocation()
|
||||
|
|
||||
p
|
||||
order by
|
||||
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
|
||||
l.getStartColumn()
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
|
||||
query predicate edges(RelevantCfgNode pred, RelevantCfgNode succ, string attr, string val) {
|
||||
exists(SuccessorType t | succ = pred.getASuccessor(t) |
|
||||
attr = "semmle.label" and
|
||||
if t instanceof SuccessorTypes::NormalSuccessor then val = "" else val = t.toString()
|
||||
)
|
||||
}
|
||||
@@ -35,6 +35,7 @@ private import codeql_ruby.AST
|
||||
private import codeql_ruby.ast.internal.AST as ASTInternal
|
||||
private import codeql_ruby.ast.internal.Scope
|
||||
private import codeql_ruby.ast.Scope
|
||||
private import codeql_ruby.ast.internal.Synthesis
|
||||
private import codeql_ruby.ast.internal.TreeSitter
|
||||
private import codeql_ruby.ast.internal.Variable
|
||||
private import codeql_ruby.controlflow.ControlFlowGraph
|
||||
@@ -85,6 +86,8 @@ module CfgScope {
|
||||
}
|
||||
|
||||
abstract private class ControlFlowTree extends AstNode {
|
||||
ControlFlowTree() { not any(Synthesis s).excludeFromControlFlowTree(this) }
|
||||
|
||||
/**
|
||||
* Holds if `first` is the first element executed within this AST node.
|
||||
*/
|
||||
@@ -284,6 +287,8 @@ module Trees {
|
||||
}
|
||||
|
||||
private class AssignOperationTree extends StandardPostOrderTree, AssignOperation {
|
||||
AssignOperationTree() { not this.getLeftOperand() instanceof VariableAccess }
|
||||
|
||||
final override ControlFlowTree getChildNode(int i) {
|
||||
result = this.getLeftOperand() and i = 0
|
||||
or
|
||||
@@ -725,6 +730,22 @@ module Trees {
|
||||
}
|
||||
}
|
||||
|
||||
private class DesugaredTree extends ControlFlowTree {
|
||||
ControlFlowTree desugared;
|
||||
|
||||
DesugaredTree() { desugared = this.getDesugared() }
|
||||
|
||||
final override predicate propagatesAbnormal(AstNode child) {
|
||||
desugared.propagatesAbnormal(child)
|
||||
}
|
||||
|
||||
final override predicate first(AstNode first) { desugared.first(first) }
|
||||
|
||||
final override predicate last(AstNode last, Completion c) { desugared.last(last, c) }
|
||||
|
||||
final override predicate succ(AstNode pred, AstNode succ, Completion c) { none() }
|
||||
}
|
||||
|
||||
private class DoBlockTree extends BodyStmtTree, DoBlock {
|
||||
/** Gets the `i`th child in the body of this block. */
|
||||
final override AstNode getBodyChild(int i, boolean rescuable) {
|
||||
|
||||
@@ -40,6 +40,12 @@ class DataFlowCallable = CfgScope;
|
||||
|
||||
class DataFlowCall extends CfgNodes::ExprNodes::CallCfgNode {
|
||||
DataFlowCallable getEnclosingCallable() { result = this.getScope() }
|
||||
|
||||
DataFlowCallable getTarget() {
|
||||
// TODO: this is a placeholder that finds a method with the same name, iff it's uniquely named.
|
||||
result =
|
||||
unique(DataFlowCallable c | c.(Method).getName() = this.getNode().(MethodCall).getMethodName())
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a viable run-time target for the call `call`. */
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2,6 +2,7 @@ private import ruby
|
||||
private import DataFlowDispatch
|
||||
private import DataFlowPrivate
|
||||
private import codeql_ruby.CFG
|
||||
private import codeql_ruby.typetracking.TypeTracker
|
||||
|
||||
/**
|
||||
* An element, viewed as a node in a data flow graph. Either an expression
|
||||
@@ -73,6 +74,37 @@ class ParameterNode extends Node, TParameterNode {
|
||||
predicate isParameterOf(Callable c, int i) { p = c.getParameter(i) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that is a source of local flow.
|
||||
*/
|
||||
class LocalSourceNode extends Node {
|
||||
LocalSourceNode() { not simpleLocalFlowStep+(any(ExprNode n), this) }
|
||||
|
||||
/** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */
|
||||
pragma[inline]
|
||||
predicate flowsTo(Node nodeTo) { hasLocalSource(nodeTo, this) }
|
||||
|
||||
/**
|
||||
* Gets a node that this node may flow to using one heap and/or interprocedural step.
|
||||
*
|
||||
* See `TypeTracker` for more details about how to use this.
|
||||
*/
|
||||
pragma[inline]
|
||||
LocalSourceNode track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
|
||||
}
|
||||
|
||||
predicate hasLocalSource(Node sink, Node source) {
|
||||
// Declaring `source` to be a `SourceNode` currently causes a redundant check in the
|
||||
// recursive case, so instead we check it explicitly here.
|
||||
source = sink and
|
||||
source instanceof LocalSourceNode
|
||||
or
|
||||
exists(Node mid |
|
||||
hasLocalSource(mid, source) and
|
||||
simpleLocalFlowStep(mid, sink)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node corresponding to expression `e`. */
|
||||
ExprNode exprNode(CfgNodes::ExprCfgNode e) { result.getExprNode() = e }
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Provides a language-independant implementation of static single assignment
|
||||
* Provides a language-independent implementation of static single assignment
|
||||
* (SSA) form.
|
||||
*/
|
||||
|
||||
@@ -316,15 +316,23 @@ private module SsaDefReaches {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the reference to `def` at index `i` in basic block `bb` is the
|
||||
* last reference to `v` inside `bb`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
|
||||
ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
|
||||
}
|
||||
|
||||
predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
|
||||
exists(ssaDefRank(def, v, bb, _, _))
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private BasicBlock getAMaybeLiveSuccessor(Definition def, BasicBlock bb) {
|
||||
result = getABasicBlockSuccessor(bb) and
|
||||
not defOccursInBlock(_, bb, def.getSourceVariable()) and
|
||||
ssaDefReachesEndOfBlock(bb, def, _)
|
||||
private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) {
|
||||
ssaDefReachesEndOfBlock(bb, def, _) and
|
||||
not defOccursInBlock(_, bb, def.getSourceVariable())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,7 +345,11 @@ private module SsaDefReaches {
|
||||
defOccursInBlock(def, bb1, _) and
|
||||
bb2 = getABasicBlockSuccessor(bb1)
|
||||
or
|
||||
exists(BasicBlock mid | varBlockReaches(def, bb1, mid) | bb2 = getAMaybeLiveSuccessor(def, mid))
|
||||
exists(BasicBlock mid |
|
||||
varBlockReaches(def, bb1, mid) and
|
||||
ssaDefReachesThroughBlock(def, mid) and
|
||||
bb2 = getABasicBlockSuccessor(mid)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,24 +360,16 @@ private module SsaDefReaches {
|
||||
*/
|
||||
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
|
||||
varBlockReaches(def, bb1, bb2) and
|
||||
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 and
|
||||
variableRead(bb2, i2, _, _)
|
||||
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
|
||||
}
|
||||
}
|
||||
|
||||
private import SsaDefReaches
|
||||
|
||||
pragma[noinline]
|
||||
private predicate ssaDefReachesEndOfBlockRec(BasicBlock bb, Definition def, SourceVariable v) {
|
||||
exists(BasicBlock idom | ssaDefReachesEndOfBlock(idom, def, v) |
|
||||
// The construction of SSA form ensures that each read of a variable is
|
||||
// dominated by its definition. An SSA definition therefore reaches a
|
||||
// control flow node if it is the _closest_ SSA definition that dominates
|
||||
// the node. If two definitions dominate a node then one must dominate the
|
||||
// other, so therefore the definition of _closest_ is given by the dominator
|
||||
// tree. Thus, reaching definitions can be calculated in terms of dominance.
|
||||
idom = getImmediateBasicBlockDominator(bb)
|
||||
)
|
||||
pragma[nomagic]
|
||||
predicate liveThrough(BasicBlock bb, SourceVariable v) {
|
||||
liveAtExit(bb, v) and
|
||||
not ssaRef(bb, _, v, SsaDef())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,9 +386,14 @@ predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable
|
||||
liveAtExit(bb, v)
|
||||
)
|
||||
or
|
||||
ssaDefReachesEndOfBlockRec(bb, def, v) and
|
||||
liveAtExit(bb, v) and
|
||||
not ssaRef(bb, _, v, SsaDef())
|
||||
// The construction of SSA form ensures that each read of a variable is
|
||||
// dominated by its definition. An SSA definition therefore reaches a
|
||||
// control flow node if it is the _closest_ SSA definition that dominates
|
||||
// the node. If two definitions dominate a node then one must dominate the
|
||||
// other, so therefore the definition of _closest_ is given by the dominator
|
||||
// tree. Thus, reaching definitions can be calculated in terms of dominance.
|
||||
ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and
|
||||
liveThrough(bb, pragma[only_bind_into](v))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -433,15 +442,22 @@ predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2
|
||||
bb2 = bb1
|
||||
)
|
||||
or
|
||||
exists(SourceVariable v | ssaDefRank(def, v, bb1, i1, _) = maxSsaRefRank(bb1, v)) and
|
||||
lastSsaRef(def, _, bb1, i1) and
|
||||
defAdjacentRead(def, bb1, bb2, i2)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate adjacentDefRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
|
||||
) {
|
||||
adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
v = def.getSourceVariable()
|
||||
}
|
||||
|
||||
private predicate adjacentDefReachesRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
|
||||
) {
|
||||
adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
exists(SourceVariable v | v = def.getSourceVariable() |
|
||||
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
|
||||
ssaRef(bb1, i1, v, SsaDef())
|
||||
or
|
||||
variableRead(bb1, i1, v, true)
|
||||
@@ -474,17 +490,19 @@ predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, Ba
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
|
||||
exists(int rnk, SourceVariable v, int j | rnk = ssaDefRank(def, v, bb, i, _) |
|
||||
exists(SourceVariable v |
|
||||
// Next reference to `v` inside `bb` is a write
|
||||
next.definesAt(v, bb, j) and
|
||||
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
|
||||
exists(int rnk, int j |
|
||||
rnk = ssaDefRank(def, v, bb, i, _) and
|
||||
next.definesAt(v, bb, j) and
|
||||
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
|
||||
)
|
||||
or
|
||||
// Can reach a write using one or more steps
|
||||
rnk = maxSsaRefRank(bb, v) and
|
||||
lastSsaRef(def, v, bb, i) and
|
||||
exists(BasicBlock bb2 |
|
||||
varBlockReaches(def, bb, bb2) and
|
||||
next.definesAt(v, bb2, j) and
|
||||
1 = ssaRefRank(bb2, j, v, SsaDef())
|
||||
1 = ssaDefRank(next, v, bb2, _, SsaDef())
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -538,7 +556,8 @@ pragma[nomagic]
|
||||
predicate lastRef(Definition def, BasicBlock bb, int i) {
|
||||
lastRefRedef(def, bb, i, _)
|
||||
or
|
||||
exists(SourceVariable v | ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v) |
|
||||
lastSsaRef(def, _, bb, i) and
|
||||
(
|
||||
// Can reach exit directly
|
||||
bb instanceof ExitBasicBlock
|
||||
or
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
|
||||
import AST
|
||||
|
||||
/** Holds if `n` appears in the desugaring of some other node. */
|
||||
predicate isDesugared(AstNode n) {
|
||||
n = any(AstNode sugar).getDesugared()
|
||||
or
|
||||
isDesugared(n.getParent())
|
||||
}
|
||||
|
||||
/**
|
||||
* The query can extend this class to control which nodes are printed.
|
||||
*/
|
||||
@@ -17,7 +24,19 @@ class PrintAstConfiguration extends string {
|
||||
/**
|
||||
* Holds if the given node should be printed.
|
||||
*/
|
||||
predicate shouldPrintNode(AstNode n) { any() }
|
||||
predicate shouldPrintNode(AstNode n) {
|
||||
not isDesugared(n)
|
||||
or
|
||||
not n.isSynthesized()
|
||||
or
|
||||
n.isSynthesized() and
|
||||
not n = any(AstNode sugar).getDesugared() and
|
||||
exists(AstNode parent |
|
||||
parent = n.getParent() and
|
||||
not parent.isSynthesized() and
|
||||
not n = parent.getDesugared()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,12 +51,11 @@ class PrintAstNode extends AstNode {
|
||||
result =
|
||||
any(int i |
|
||||
this =
|
||||
rank[i](AstNode p |
|
||||
|
|
||||
p
|
||||
order by
|
||||
p.getLocation().getFile().getBaseName(), p.getLocation().getFile().getAbsolutePath(),
|
||||
p.getLocation().getStartLine(), p.getLocation().getStartColumn()
|
||||
rank[i](AstNode p, Location l, File f |
|
||||
l = p.getLocation() and
|
||||
f = l.getFile()
|
||||
|
|
||||
p order by f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn()
|
||||
)
|
||||
).toString()
|
||||
}
|
||||
@@ -75,10 +93,10 @@ query predicate nodes(PrintAstNode node, string key, string value) {
|
||||
query predicate edges(PrintAstNode source, PrintAstNode target, string key, string value) {
|
||||
source.shouldPrint() and
|
||||
target.shouldPrint() and
|
||||
target = source.getAChild() and
|
||||
target = source.getChild(_) and
|
||||
(
|
||||
key = "semmle.label" and
|
||||
value = concat(string name | source.getChild(name) = target | name, "/")
|
||||
value = strictconcat(string name | source.getChild(name) = target | name, "/")
|
||||
or
|
||||
key = "semmle.order" and
|
||||
value = target.getProperty("semmle.order")
|
||||
|
||||
420
ql/src/codeql_ruby/typetracking/TypeTracker.qll
Normal file
420
ql/src/codeql_ruby/typetracking/TypeTracker.qll
Normal file
@@ -0,0 +1,420 @@
|
||||
/** Step Summaries and Type Tracking */
|
||||
|
||||
private import TypeTrackerSpecific
|
||||
|
||||
/**
|
||||
* Any string that may appear as the name of a piece of content. This will usually include things like:
|
||||
* - Attribute names (in Python)
|
||||
* - Property names (in JavaScript)
|
||||
*
|
||||
* In general, this can also be used to model things like stores to specific list indices. To ensure
|
||||
* correctness, it is important that
|
||||
*
|
||||
* - different types of content do not have overlapping names, and
|
||||
* - the empty string `""` is not a valid piece of content, as it is used to indicate the absence of
|
||||
* content instead.
|
||||
*/
|
||||
class ContentName extends string {
|
||||
ContentName() { this = getPossibleContentName() }
|
||||
}
|
||||
|
||||
/** Either a content name, or the empty string (representing no content). */
|
||||
class OptionalContentName extends string {
|
||||
OptionalContentName() { this instanceof ContentName or this = "" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
private newtype TStepSummary =
|
||||
LevelStep() or
|
||||
CallStep() or
|
||||
ReturnStep() or
|
||||
StoreStep(ContentName content) or
|
||||
LoadStep(ContentName content)
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
|
||||
*
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
class StepSummary extends TStepSummary {
|
||||
/** Gets a textual representation of this step summary. */
|
||||
string toString() {
|
||||
this instanceof LevelStep and result = "level"
|
||||
or
|
||||
this instanceof CallStep and result = "call"
|
||||
or
|
||||
this instanceof ReturnStep and result = "return"
|
||||
or
|
||||
exists(string content | this = StoreStep(content) | result = "store " + content)
|
||||
or
|
||||
exists(string content | this = LoadStep(content) | result = "load " + content)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for updating step summaries (`StepSummary`s). */
|
||||
module StepSummary {
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
cached
|
||||
predicate step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
|
||||
exists(Node mid | nodeFrom.flowsTo(mid) and smallstep(mid, nodeTo, summary))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* Unlike `StepSummary::step`, this predicate does not compress
|
||||
* type-preserving steps.
|
||||
*/
|
||||
predicate smallstep(Node nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
|
||||
jumpStep(nodeFrom, nodeTo) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
callStep(nodeFrom, nodeTo) and summary = CallStep()
|
||||
or
|
||||
returnStep(nodeFrom, nodeTo) and
|
||||
summary = ReturnStep()
|
||||
or
|
||||
exists(string content |
|
||||
localSourceStoreStep(nodeFrom, nodeTo, content) and
|
||||
summary = StoreStep(content)
|
||||
or
|
||||
basicLoadStep(nodeFrom, nodeTo, content) and summary = LoadStep(content)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
|
||||
*
|
||||
* Note that `nodeTo` will always be a local source node that flows to the place where the content
|
||||
* is written in `basicStoreStep`. This may lead to the flow of information going "back in time"
|
||||
* from the point of view of the execution of the program.
|
||||
*
|
||||
* For instance, if we interpret attribute writes in Python as writing to content with the same
|
||||
* name as the attribute and consider the following snippet
|
||||
*
|
||||
* ```python
|
||||
* def foo(y):
|
||||
* x = Foo()
|
||||
* bar(x)
|
||||
* x.attr = y
|
||||
* baz(x)
|
||||
*
|
||||
* def bar(x):
|
||||
* z = x.attr
|
||||
* ```
|
||||
* for the attribute write `x.attr = y`, we will have `content` being the literal string `"attr"`,
|
||||
* `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the
|
||||
* function. This means we will track the fact that `x.attr` can have the type of `y` into the
|
||||
* assignment to `z` inside `bar`, even though this attribute write happens _after_ `bar` is called.
|
||||
*/
|
||||
predicate localSourceStoreStep(Node nodeFrom, LocalSourceNode nodeTo, string content) {
|
||||
exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content))
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalContentName content)
|
||||
|
||||
/**
|
||||
* Summary of the steps needed to track a value to a given dataflow node.
|
||||
*
|
||||
* This can be used to track objects that implement a certain API in order to
|
||||
* recognize calls to that API. Note that type-tracking does not by itself provide a
|
||||
* source/sink relation, that is, it may determine that a node has a given type,
|
||||
* but it won't determine where that type came from.
|
||||
*
|
||||
* It is recommended that all uses of this type are written in the following form,
|
||||
* for tracking some type `myType`:
|
||||
* ```ql
|
||||
* DataFlow::LocalSourceNode myType(DataFlow::TypeTracker t) {
|
||||
* t.start() and
|
||||
* result = < source of myType >
|
||||
* or
|
||||
* exists (DataFlow::TypeTracker t2 |
|
||||
* result = myType(t2).track(t2, t)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::Node myType() { myType(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
* ```
|
||||
*
|
||||
* Instead of `result = myType(t2).track(t2, t)`, you can also use the equivalent
|
||||
* `t = t2.step(myType(t2), result)`. If you additionally want to track individual
|
||||
* intra-procedural steps, use `t = t2.smallstep(myCallback(t2), result)`.
|
||||
*/
|
||||
class TypeTracker extends TTypeTracker {
|
||||
Boolean hasCall;
|
||||
OptionalContentName content;
|
||||
|
||||
TypeTracker() { this = MkTypeTracker(hasCall, content) }
|
||||
|
||||
/** Gets the summary resulting from appending `step` to this type-tracking summary. */
|
||||
cached
|
||||
TypeTracker append(StepSummary step) {
|
||||
step = LevelStep() and result = this
|
||||
or
|
||||
step = CallStep() and result = MkTypeTracker(true, content)
|
||||
or
|
||||
step = ReturnStep() and hasCall = false and result = this
|
||||
or
|
||||
step = LoadStep(content) and result = MkTypeTracker(hasCall, "")
|
||||
or
|
||||
exists(string p | step = StoreStep(p) and content = "" and result = MkTypeTracker(hasCall, p))
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
string toString() {
|
||||
exists(string withCall, string withContent |
|
||||
(if hasCall = true then withCall = "with" else withCall = "without") and
|
||||
(if content != "" then withContent = " with content " + content else withContent = "") and
|
||||
result = "type tracker " + withCall + " call steps" + withContent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking.
|
||||
*/
|
||||
predicate start() { hasCall = false and content = "" }
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking, and the value starts in the content named `contentName`.
|
||||
* The type tracking only ends after the content has been loaded.
|
||||
*/
|
||||
predicate startInContent(ContentName contentName) { hasCall = false and content = contentName }
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking
|
||||
* when tracking a parameter into a call, but not out of it.
|
||||
*/
|
||||
predicate call() { hasCall = true and content = "" }
|
||||
|
||||
/**
|
||||
* Holds if this is the end point of type tracking.
|
||||
*/
|
||||
predicate end() { content = "" }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Holds if this type has been tracked into a call.
|
||||
*/
|
||||
boolean hasCall() { result = hasCall }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Gets the content associated with this type tracker.
|
||||
*/
|
||||
string getContent() { result = content }
|
||||
|
||||
/**
|
||||
* Gets a type tracker that starts where this one has left off to allow continued
|
||||
* tracking.
|
||||
*
|
||||
* This predicate is only defined if the type is not associated to a piece of content.
|
||||
*/
|
||||
TypeTracker continue() { content = "" and result = this }
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(nodeFrom, pragma[only_bind_out](nodeTo), pragma[only_bind_into](summary)) and
|
||||
result = this.append(pragma[only_bind_into](summary))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* Unlike `TypeTracker::step`, this predicate exposes all edges
|
||||
* in the flow graph, and not just the edges between `Node`s.
|
||||
* It may therefore be less performant.
|
||||
*
|
||||
* Type tracking predicates using small steps typically take the following form:
|
||||
* ```ql
|
||||
* DataFlow::Node myType(DataFlow::TypeTracker t) {
|
||||
* t.start() and
|
||||
* result = < source of myType >
|
||||
* or
|
||||
* exists (DataFlow::TypeTracker t2 |
|
||||
* t = t2.smallstep(myType(t2), result)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::Node myType() {
|
||||
* result = myType(DataFlow::TypeTracker::end())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
|
||||
result = this.append(summary)
|
||||
)
|
||||
or
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
result = this
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for implementing custom `TypeTracker`s. */
|
||||
module TypeTracker {
|
||||
/**
|
||||
* Gets a valid end point of type tracking.
|
||||
*/
|
||||
TypeTracker end() { result.end() }
|
||||
}
|
||||
|
||||
private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, OptionalContentName content)
|
||||
|
||||
/**
|
||||
* Summary of the steps needed to back-track a use of a value to a given dataflow node.
|
||||
*
|
||||
* This can for example be used to track callbacks that are passed to a certain API,
|
||||
* so we can model specific parameters of that callback as having a certain type.
|
||||
*
|
||||
* Note that type back-tracking does not provide a source/sink relation, that is,
|
||||
* it may determine that a node will be used in an API call somewhere, but it won't
|
||||
* determine exactly where that use was, or the path that led to the use.
|
||||
*
|
||||
* It is recommended that all uses of this type are written in the following form,
|
||||
* for back-tracking some callback type `myCallback`:
|
||||
*
|
||||
* ```ql
|
||||
* DataFlow::LocalSourceNode myCallback(DataFlow::TypeBackTracker t) {
|
||||
* t.start() and
|
||||
* result = (< some API call >).getArgument(< n >).getALocalSource()
|
||||
* or
|
||||
* exists (DataFlow::TypeBackTracker t2 |
|
||||
* result = myCallback(t2).backtrack(t2, t)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::LocalSourceNode myCallback() { result = myCallback(DataFlow::TypeBackTracker::end()) }
|
||||
* ```
|
||||
*
|
||||
* Instead of `result = myCallback(t2).backtrack(t2, t)`, you can also use the equivalent
|
||||
* `t2 = t.step(result, myCallback(t2))`. If you additionally want to track individual
|
||||
* intra-procedural steps, use `t2 = t.smallstep(result, myCallback(t2))`.
|
||||
*/
|
||||
class TypeBackTracker extends TTypeBackTracker {
|
||||
Boolean hasReturn;
|
||||
string content;
|
||||
|
||||
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
|
||||
|
||||
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
|
||||
TypeBackTracker prepend(StepSummary step) {
|
||||
step = LevelStep() and result = this
|
||||
or
|
||||
step = CallStep() and hasReturn = false and result = this
|
||||
or
|
||||
step = ReturnStep() and result = MkTypeBackTracker(true, content)
|
||||
or
|
||||
exists(string p |
|
||||
step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p)
|
||||
)
|
||||
or
|
||||
step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
string toString() {
|
||||
exists(string withReturn, string withContent |
|
||||
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
|
||||
(if content != "" then withContent = " with content " + content else withContent = "") and
|
||||
result = "type back-tracker " + withReturn + " return steps" + withContent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking.
|
||||
*/
|
||||
predicate start() { hasReturn = false and content = "" }
|
||||
|
||||
/**
|
||||
* Holds if this is the end point of type tracking.
|
||||
*/
|
||||
predicate end() { content = "" }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Holds if this type has been back-tracked into a call through return edge.
|
||||
*/
|
||||
boolean hasReturn() { result = hasReturn }
|
||||
|
||||
/**
|
||||
* Gets a type tracker that starts where this one has left off to allow continued
|
||||
* tracking.
|
||||
*
|
||||
* This predicate is only defined if the type has not been tracked into a piece of content.
|
||||
*/
|
||||
TypeBackTracker continue() { content = "" and result = this }
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a backwards
|
||||
* heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeBackTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and
|
||||
this = result.prepend(pragma[only_bind_into](summary))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a backwards
|
||||
* local, heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
|
||||
*
|
||||
* Unlike `TypeBackTracker::step`, this predicate exposes all edges
|
||||
* in the flowgraph, and not just the edges between
|
||||
* `LocalSourceNode`s. It may therefore be less performant.
|
||||
*
|
||||
* Type tracking predicates using small steps typically take the following form:
|
||||
* ```ql
|
||||
* DataFlow::Node myType(DataFlow::TypeBackTracker t) {
|
||||
* t.start() and
|
||||
* result = < some API call >.getArgument(< n >)
|
||||
* or
|
||||
* exists (DataFlow::TypeBackTracker t2 |
|
||||
* t = t2.smallstep(result, myType(t2))
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::Node myType() {
|
||||
* result = myType(DataFlow::TypeBackTracker::end())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
|
||||
this = result.prepend(summary)
|
||||
)
|
||||
or
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
this = result
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for implementing custom `TypeBackTracker`s. */
|
||||
module TypeBackTracker {
|
||||
/**
|
||||
* Gets a valid end point of type back-tracking.
|
||||
*/
|
||||
TypeBackTracker end() { result.end() }
|
||||
}
|
||||
118
ql/src/codeql_ruby/typetracking/TypeTrackerSpecific.qll
Normal file
118
ql/src/codeql_ruby/typetracking/TypeTrackerSpecific.qll
Normal file
@@ -0,0 +1,118 @@
|
||||
private import codeql_ruby.AST as AST
|
||||
private import codeql_ruby.dataflow.internal.DataFlowPublic as DataFlowPublic
|
||||
private import codeql_ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
|
||||
private import codeql_ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
|
||||
private import codeql_ruby.controlflow.CfgNodes
|
||||
|
||||
class Node = DataFlowPublic::Node;
|
||||
|
||||
class LocalSourceNode = DataFlowPublic::LocalSourceNode;
|
||||
|
||||
predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStep/2;
|
||||
|
||||
predicate jumpStep = DataFlowPrivate::jumpStep/2;
|
||||
|
||||
/**
|
||||
* Gets the name of a possible piece of content. This will usually include things like
|
||||
*
|
||||
* - Attribute names (in Python)
|
||||
* - Property names (in JavaScript)
|
||||
*/
|
||||
string getPossibleContentName() { result = getSetterCallAttributeName(_) }
|
||||
|
||||
/** Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. */
|
||||
predicate callStep(
|
||||
DataFlowPrivate::ArgumentNode nodeFrom, DataFlowPrivate::ExplicitParameterNode nodeTo
|
||||
) {
|
||||
exists(DataFlowDispatch::DataFlowCall call, DataFlowDispatch::DataFlowCallable callable, int i |
|
||||
call.getTarget() = callable and
|
||||
nodeFrom.argumentOf(call, i) and
|
||||
nodeTo.isParameterOf(callable, i)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. */
|
||||
predicate returnStep(DataFlowPrivate::ReturnNode nodeFrom, Node nodeTo) {
|
||||
exists(DataFlowDispatch::DataFlowCall call |
|
||||
nodeFrom.getEnclosingCallable() = call.getTarget() and
|
||||
nodeTo.asExpr().getNode() = call.getNode()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is being written to the `content` content of the object
|
||||
* in `nodeTo`.
|
||||
*
|
||||
* Note that the choice of `nodeTo` does not have to make sense
|
||||
* "chronologically". All we care about is whether the `content` content of
|
||||
* `nodeTo` can have a specific type, and the assumption is that if a specific
|
||||
* type appears here, then any access of that particular content can yield
|
||||
* something of that particular type.
|
||||
*
|
||||
* Thus, in an example such as
|
||||
*
|
||||
* ```rb
|
||||
* def foo(y)
|
||||
* x = Foo.new
|
||||
* bar(x)
|
||||
* x.content = y
|
||||
* baz(x)
|
||||
* end
|
||||
*
|
||||
* def bar(x)
|
||||
* z = x.content
|
||||
* end
|
||||
* ```
|
||||
* for the content write `x.content = y`, we will have `content` being the
|
||||
* literal string `"content"`, `nodeFrom` will be `y`, and `nodeTo` will be the
|
||||
* `Foo` object created on the first line of the function. This means we will
|
||||
* track the fact that `x.content` can have the type of `y` into the assignment
|
||||
* to `z` inside `bar`, even though this content write happens _after_ `bar` is
|
||||
* called.
|
||||
*/
|
||||
predicate basicStoreStep(Node nodeFrom, LocalSourceNode nodeTo, string content) {
|
||||
// TODO: support SetterMethodCall inside TuplePattern
|
||||
exists(ExprNodes::AssignmentCfgNode assignment, ExprNodes::MethodCallCfgNode call |
|
||||
assignment.getLhs() = call and
|
||||
content = getSetterCallAttributeName(call.getExpr()) and
|
||||
nodeTo.(DataFlowPublic::ExprNode).getExprNode() = call.getReceiver() and
|
||||
call.getExpr() instanceof AST::SetterMethodCall and
|
||||
assignment.getRhs() = nodeFrom.(DataFlowPublic::ExprNode).getExprNode()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the attribute being set by the setter method call, i.e.
|
||||
* the name of the setter method without the trailing `=`. In the following
|
||||
* example, the result is `"bar"`.
|
||||
*
|
||||
* ```rb
|
||||
* foo.bar = 1
|
||||
* ```
|
||||
*/
|
||||
private string getSetterCallAttributeName(AST::SetterMethodCall call) {
|
||||
// TODO: this should be exposed in `SetterMethodCall`
|
||||
not call instanceof AST::ElementReference and
|
||||
exists(string setterName |
|
||||
setterName = call.getMethodName() and result = setterName.prefix(setterName.length() - 1)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`.
|
||||
*/
|
||||
predicate basicLoadStep(Node nodeFrom, Node nodeTo, string content) {
|
||||
exists(ExprNodes::MethodCallCfgNode call |
|
||||
call.getExpr().getNumberOfArguments() = 0 and
|
||||
content = call.getExpr().(AST::MethodCall).getMethodName() and
|
||||
nodeFrom.asExpr() = call.getReceiver() and
|
||||
nodeTo.asExpr() = call
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility class that is equivalent to `boolean` but does not require type joining.
|
||||
*/
|
||||
class Boolean extends boolean {
|
||||
Boolean() { this = true or this = false }
|
||||
}
|
||||
Reference in New Issue
Block a user