mirror of
https://github.com/github/codeql.git
synced 2026-05-05 05:35:13 +02:00
Merge remote-tracking branch 'origin/main' into nickrolfe/regex_injection
This commit is contained in:
@@ -18,6 +18,27 @@ private import ast.internal.Scope
|
||||
private import ast.internal.Synthesis
|
||||
private import ast.internal.TreeSitter
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
ModuleBase getEnclosingModule(Scope s) {
|
||||
result = s
|
||||
or
|
||||
not s instanceof ModuleBase and result = getEnclosingModule(s.getOuterScope())
|
||||
}
|
||||
|
||||
cached
|
||||
MethodBase getEnclosingMethod(Scope s) {
|
||||
result = s
|
||||
or
|
||||
not s instanceof MethodBase and
|
||||
not s instanceof ModuleBase and
|
||||
result = getEnclosingMethod(s.getOuterScope())
|
||||
}
|
||||
}
|
||||
|
||||
private import Cached
|
||||
|
||||
/**
|
||||
* A node in the abstract syntax tree. This class is the base class for all Ruby
|
||||
* program elements.
|
||||
@@ -39,20 +60,10 @@ class AstNode extends TAstNode {
|
||||
final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") }
|
||||
|
||||
/** Gets the enclosing module, if any. */
|
||||
ModuleBase getEnclosingModule() {
|
||||
exists(Scope::Range s |
|
||||
s = scopeOf(toGeneratedInclSynth(this)) and
|
||||
toGeneratedInclSynth(result) = s.getEnclosingModule()
|
||||
)
|
||||
}
|
||||
ModuleBase getEnclosingModule() { result = getEnclosingModule(scopeOfInclSynth(this)) }
|
||||
|
||||
/** Gets the enclosing method, if any. */
|
||||
MethodBase getEnclosingMethod() {
|
||||
exists(Scope::Range s |
|
||||
s = scopeOf(toGeneratedInclSynth(this)) and
|
||||
toGeneratedInclSynth(result) = s.getEnclosingMethod()
|
||||
)
|
||||
}
|
||||
MethodBase getEnclosingMethod() { result = getEnclosingMethod(scopeOfInclSynth(this)) }
|
||||
|
||||
/** Gets a textual representation of this node. */
|
||||
cached
|
||||
|
||||
@@ -596,6 +596,37 @@ module OrmInstantiation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that may set or unset Cross-site request forgery protection.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `CSRFProtectionSetting::Range` instead.
|
||||
*/
|
||||
class CSRFProtectionSetting extends DataFlow::Node instanceof CSRFProtectionSetting::Range {
|
||||
/**
|
||||
* Gets the boolean value corresponding to if CSRF protection is enabled
|
||||
* (`true`) or disabled (`false`) by this node.
|
||||
*/
|
||||
boolean getVerificationSetting() { result = super.getVerificationSetting() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new CSRF protection setting APIs. */
|
||||
module CSRFProtectionSetting {
|
||||
/**
|
||||
* A data-flow node that may set or unset Cross-site request forgery protection.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `CSRFProtectionSetting` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the boolean value corresponding to if CSRF protection is enabled
|
||||
* (`true`) or disabled (`false`) by this node.
|
||||
*/
|
||||
abstract boolean getVerificationSetting();
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides classes for modeling path-related APIs. */
|
||||
module Path {
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActiveStorage
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.Rails
|
||||
private import codeql.ruby.frameworks.StandardLibrary
|
||||
private import codeql.ruby.frameworks.Files
|
||||
private import codeql.ruby.frameworks.HttpClients
|
||||
|
||||
@@ -583,7 +583,7 @@ class ForExpr extends Loop, TForExpr {
|
||||
final override string getAPrimaryQlClass() { result = "ForExpr" }
|
||||
|
||||
/** Gets the body of this `for` loop. */
|
||||
final override Stmt getBody() { toGenerated(result) = g.getBody() }
|
||||
final override StmtSequence getBody() { toGenerated(result) = g.getBody() }
|
||||
|
||||
/** Gets the pattern representing the iteration argument. */
|
||||
final Pattern getPattern() { toGenerated(result) = g.getPattern() }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import internal.AST
|
||||
private import internal.Expr
|
||||
private import internal.TreeSitter
|
||||
|
||||
/**
|
||||
@@ -91,90 +92,19 @@ class StmtSequence extends Expr, TStmtSequence {
|
||||
}
|
||||
}
|
||||
|
||||
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 Ruby::Then g;
|
||||
|
||||
Then() { this = TThen(g) }
|
||||
|
||||
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
final override string toString() { result = "then ..." }
|
||||
}
|
||||
|
||||
private class Else extends StmtSequence, TElse {
|
||||
private Ruby::Else g;
|
||||
|
||||
Else() { this = TElse(g) }
|
||||
|
||||
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
final override string toString() { result = "else ..." }
|
||||
}
|
||||
|
||||
private class Do extends StmtSequence, TDo {
|
||||
private Ruby::Do g;
|
||||
|
||||
Do() { this = TDo(g) }
|
||||
|
||||
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
final override string toString() { result = "do ..." }
|
||||
}
|
||||
|
||||
private class Ensure extends StmtSequence, TEnsure {
|
||||
private Ruby::Ensure g;
|
||||
|
||||
Ensure() { this = TEnsure(g) }
|
||||
|
||||
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
final override string toString() { result = "ensure ..." }
|
||||
}
|
||||
|
||||
/**
|
||||
* A sequence of statements representing the body of a method, class, module,
|
||||
* or do-block. That is, any body that may also include rescue/ensure/else
|
||||
* statements.
|
||||
*/
|
||||
class BodyStmt extends StmtSequence, TBodyStmt {
|
||||
// Not defined by dispatch, as it should not be exposed
|
||||
private Ruby::AstNode getChild(int i) {
|
||||
result = any(Ruby::Method g | this = TMethod(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::SingletonMethod g | this = TSingletonMethod(g)).getChild(i)
|
||||
or
|
||||
exists(Ruby::Lambda g | this = TLambda(g) |
|
||||
result = g.getBody().(Ruby::DoBlock).getChild(i) or
|
||||
result = g.getBody().(Ruby::Block).getChild(i)
|
||||
)
|
||||
or
|
||||
result = any(Ruby::DoBlock g | this = TDoBlock(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::Program g | this = TToplevel(g)).getChild(i) and
|
||||
not result instanceof Ruby::BeginBlock
|
||||
or
|
||||
result = any(Ruby::Class g | this = TClassDeclaration(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::SingletonClass g | this = TSingletonClass(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::Module g | this = TModuleDeclaration(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::Begin g | this = TBeginExpr(g)).getChild(i)
|
||||
}
|
||||
|
||||
final override Stmt getStmt(int n) {
|
||||
result =
|
||||
rank[n + 1](AstNode node, int i |
|
||||
toGenerated(node) = this.getChild(i) and
|
||||
not node instanceof Else and
|
||||
not node instanceof RescueClause and
|
||||
not node instanceof Ensure
|
||||
toGenerated(result) =
|
||||
rank[n + 1](Ruby::AstNode node, int i |
|
||||
node = getBodyStmtChild(this, i) and
|
||||
not node instanceof Ruby::Else and
|
||||
not node instanceof Ruby::Rescue and
|
||||
not node instanceof Ruby::Ensure
|
||||
|
|
||||
node order by i
|
||||
)
|
||||
@@ -183,17 +113,25 @@ class BodyStmt extends StmtSequence, TBodyStmt {
|
||||
/** Gets the `n`th rescue clause in this block. */
|
||||
final RescueClause getRescue(int n) {
|
||||
result =
|
||||
rank[n + 1](RescueClause node, int i | toGenerated(node) = this.getChild(i) | node order by i)
|
||||
rank[n + 1](RescueClause node, int i |
|
||||
toGenerated(node) = getBodyStmtChild(this, i)
|
||||
|
|
||||
node order by i
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a rescue clause in this block. */
|
||||
final RescueClause getARescue() { result = this.getRescue(_) }
|
||||
|
||||
/** Gets the `else` clause in this block, if any. */
|
||||
final StmtSequence getElse() { result = unique(Else s | toGenerated(s) = getChild(_)) }
|
||||
final StmtSequence getElse() {
|
||||
result = unique(Else s | toGenerated(s) = getBodyStmtChild(this, _))
|
||||
}
|
||||
|
||||
/** Gets the `ensure` clause in this block, if any. */
|
||||
final StmtSequence getEnsure() { result = unique(Ensure s | toGenerated(s) = getChild(_)) }
|
||||
final StmtSequence getEnsure() {
|
||||
result = unique(Ensure s | toGenerated(s) = getBodyStmtChild(this, _))
|
||||
}
|
||||
|
||||
final predicate hasEnsure() { exists(this.getEnsure()) }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ private import codeql.ruby.AST
|
||||
private import codeql.ruby.controlflow.ControlFlowGraph
|
||||
private import internal.AST
|
||||
private import internal.TreeSitter
|
||||
private import internal.Method
|
||||
|
||||
/** A callable. */
|
||||
class Callable extends StmtSequence, Expr, Scope, TCallable {
|
||||
@@ -212,16 +213,6 @@ class DoBlock extends Block, BodyStmt, TDoBlock {
|
||||
* ```
|
||||
*/
|
||||
class BraceBlock extends Block, TBraceBlock {
|
||||
private Ruby::Block g;
|
||||
|
||||
BraceBlock() { this = TBraceBlock(g) }
|
||||
|
||||
final override Parameter getParameter(int n) {
|
||||
toGenerated(result) = g.getParameters().getChild(n)
|
||||
}
|
||||
|
||||
final override Stmt getStmt(int i) { toGenerated(result) = g.getChild(i) }
|
||||
|
||||
final override string toString() { result = "{ ... }" }
|
||||
|
||||
final override string getAPrimaryQlClass() { result = "BraceBlock" }
|
||||
|
||||
@@ -57,7 +57,11 @@ class NamedParameter extends Parameter, TNamedParameter {
|
||||
final VariableAccess getAnAccess() { result = this.getVariable().getAnAccess() }
|
||||
|
||||
/** Gets the access that defines the underlying local variable. */
|
||||
final VariableAccess getDefiningAccess() { result = this.getVariable().getDefiningAccess() }
|
||||
final VariableAccess getDefiningAccess() {
|
||||
result = this.getVariable().getDefiningAccess()
|
||||
or
|
||||
result = this.(SimpleParameterSynthImpl).getDefininingAccess()
|
||||
}
|
||||
|
||||
override AstNode getAChild(string pred) {
|
||||
result = super.getAChild(pred)
|
||||
@@ -68,14 +72,12 @@ class NamedParameter extends Parameter, TNamedParameter {
|
||||
}
|
||||
|
||||
/** A simple (normal) parameter. */
|
||||
class SimpleParameter extends NamedParameter, PatternParameter, VariablePattern, TSimpleParameter {
|
||||
private Ruby::Identifier g;
|
||||
class SimpleParameter extends NamedParameter, PatternParameter, VariablePattern, TSimpleParameter instanceof SimpleParameterImpl {
|
||||
final override string getName() { result = SimpleParameterImpl.super.getNameImpl() }
|
||||
|
||||
SimpleParameter() { this = TSimpleParameter(g) }
|
||||
|
||||
final override string getName() { result = g.getValue() }
|
||||
|
||||
final override LocalVariable getVariable() { result = TLocalVariableReal(_, _, g) }
|
||||
final override LocalVariable getVariable() {
|
||||
result = SimpleParameterImpl.super.getVariableImpl()
|
||||
}
|
||||
|
||||
final override LocalVariable getAVariable() { result = this.getVariable() }
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ private import internal.AST
|
||||
private import internal.Pattern
|
||||
private import internal.TreeSitter
|
||||
private import internal.Variable
|
||||
private import internal.Parameter
|
||||
|
||||
/** A pattern. */
|
||||
class Pattern extends AstNode {
|
||||
@@ -15,6 +16,8 @@ class Pattern extends AstNode {
|
||||
implicitParameterAssignmentNode(toGenerated(this), _)
|
||||
or
|
||||
this = getSynthChild(any(AssignExpr ae), 0)
|
||||
or
|
||||
this instanceof SimpleParameterImpl
|
||||
}
|
||||
|
||||
/** Gets a variable used in (or introduced by) this pattern. */
|
||||
|
||||
@@ -3,22 +3,20 @@ private import internal.AST
|
||||
private import internal.Scope
|
||||
private import internal.TreeSitter
|
||||
|
||||
class Scope extends AstNode, TScopeType {
|
||||
private Scope::Range range;
|
||||
/**
|
||||
* A variable scope. This is either a top-level (file), a module, a class,
|
||||
* or a callable.
|
||||
*/
|
||||
class Scope extends AstNode, TScopeType instanceof ScopeImpl {
|
||||
/** Gets the outer scope, if any. */
|
||||
Scope getOuterScope() { result = super.getOuterScopeImpl() }
|
||||
|
||||
Scope() { range = toGenerated(this) }
|
||||
/** Gets a variable declared in this scope. */
|
||||
Variable getAVariable() { result = super.getAVariableImpl() }
|
||||
|
||||
/** Gets the scope in which this scope is nested, if any. */
|
||||
Scope getOuterScope() { toGenerated(result) = range.getOuterScope() }
|
||||
|
||||
/** Gets a variable that is declared in this scope. */
|
||||
final Variable getAVariable() { result.getDeclaringScope() = this }
|
||||
|
||||
/** Gets the variable declared in this scope with the given name, if any. */
|
||||
final Variable getVariable(string name) {
|
||||
result = this.getAVariable() and
|
||||
result.getName() = name
|
||||
}
|
||||
/** Gets the variable named `name` declared in this scope. */
|
||||
Variable getVariable(string name) { result = super.getVariableImpl(name) }
|
||||
}
|
||||
|
||||
/** A scope in which a `self` variable exists. */
|
||||
class SelfScope extends Scope, TSelfScopeType { }
|
||||
|
||||
@@ -5,6 +5,7 @@ private import codeql.Locations
|
||||
private import internal.AST
|
||||
private import internal.TreeSitter
|
||||
private import internal.Variable
|
||||
private import internal.Parameter
|
||||
|
||||
/** A variable declared in a scope. */
|
||||
class Variable instanceof VariableImpl {
|
||||
@@ -50,7 +51,7 @@ class LocalVariable extends Variable, TLocalVariable {
|
||||
*
|
||||
* `x` is a captured variable, whereas `y` is not.
|
||||
*/
|
||||
predicate isCaptured() { this.getAnAccess().isCapturedAccess() }
|
||||
final predicate isCaptured() { this.getAnAccess().isCapturedAccess() }
|
||||
}
|
||||
|
||||
/** A global variable. */
|
||||
@@ -110,7 +111,11 @@ class VariableAccess extends Expr instanceof VariableAccessImpl {
|
||||
* the access to `elements` in the parameter list is an implicit assignment,
|
||||
* as is the first access to `e`.
|
||||
*/
|
||||
predicate isImplicitWrite() { implicitWriteAccess(toGenerated(this)) }
|
||||
predicate isImplicitWrite() {
|
||||
implicitWriteAccess(toGenerated(this))
|
||||
or
|
||||
this = any(SimpleParameterSynthImpl p).getDefininingAccess()
|
||||
}
|
||||
|
||||
final override string toString() { result = VariableAccessImpl.super.toString() }
|
||||
}
|
||||
@@ -147,7 +152,7 @@ class LocalVariableAccess extends VariableAccess instanceof LocalVariableAccessI
|
||||
* the access to `x` in the first `puts x` is a captured access, while
|
||||
* the access to `x` in the second `puts x` is not.
|
||||
*/
|
||||
predicate isCapturedAccess() { isCapturedAccess(this) }
|
||||
final predicate isCapturedAccess() { isCapturedAccess(this) }
|
||||
}
|
||||
|
||||
/** An access to a local variable where the value is updated. */
|
||||
@@ -195,10 +200,4 @@ class SelfVariableAccess extends LocalVariableAccess instanceof SelfVariableAcce
|
||||
}
|
||||
|
||||
/** An access to the `self` variable where the value is read. */
|
||||
class SelfVariableReadAccess extends SelfVariableAccess, VariableReadAccess {
|
||||
// We override the definition in `LocalVariableAccess` because it gives the
|
||||
// wrong result for synthesised `self` variables.
|
||||
override predicate isCapturedAccess() {
|
||||
this.getVariable().getDeclaringScope() != this.getCfgScope()
|
||||
}
|
||||
}
|
||||
class SelfVariableReadAccess extends SelfVariableAccess, VariableReadAccess { }
|
||||
|
||||
@@ -93,7 +93,8 @@ private module Cached {
|
||||
} or
|
||||
TBlockArgument(Ruby::BlockArgument g) or
|
||||
TBlockParameter(Ruby::BlockParameter g) or
|
||||
TBraceBlock(Ruby::Block g) { not g.getParent() instanceof Ruby::Lambda } or
|
||||
TBraceBlockSynth(AST::AstNode parent, int i) { mkSynthChild(BraceBlockKind(), parent, i) } or
|
||||
TBraceBlockReal(Ruby::Block g) { not g.getParent() instanceof Ruby::Lambda } or
|
||||
TBreakStmt(Ruby::Break g) or
|
||||
TCaseEqExpr(Ruby::Binary g) { g instanceof @ruby_binary_equalequalequal } or
|
||||
TCaseExpr(Ruby::Case g) or
|
||||
@@ -131,7 +132,6 @@ private module Cached {
|
||||
TFalseLiteral(Ruby::False g) or
|
||||
TFloatLiteral(Ruby::Float g) { not any(Ruby::Rational r).getChild() = g } or
|
||||
TForExpr(Ruby::For g) or
|
||||
TForIn(Ruby::In g) or // TODO REMOVE
|
||||
TForwardParameter(Ruby::ForwardParameter g) or
|
||||
TForwardArgument(Ruby::ForwardArgument g) or
|
||||
TGEExpr(Ruby::Binary g) { g instanceof @ruby_binary_rangleequal } or
|
||||
@@ -238,7 +238,10 @@ private module Cached {
|
||||
TSelfSynth(AST::AstNode parent, int i, AST::SelfVariable v) {
|
||||
mkSynthChild(SelfKind(v), parent, i)
|
||||
} or
|
||||
TSimpleParameter(Ruby::Identifier g) { g instanceof Parameter::Range } or
|
||||
TSimpleParameterReal(Ruby::Identifier g) { g instanceof Parameter::Range } or
|
||||
TSimpleParameterSynth(AST::AstNode parent, int i) {
|
||||
mkSynthChild(SimpleParameterKind(), parent, i)
|
||||
} or
|
||||
TSimpleSymbolLiteral(Ruby::SimpleSymbol g) or
|
||||
TSingletonClass(Ruby::SingletonClass g) or
|
||||
TSingletonMethod(Ruby::SingletonMethod g) or
|
||||
@@ -291,12 +294,12 @@ private module Cached {
|
||||
TAssignModuloExpr or TAssignMulExpr or TAssignRShiftExpr or TAssignSubExpr or
|
||||
TBareStringLiteral or TBareSymbolLiteral or TBeginBlock or TBeginExpr or
|
||||
TBitwiseAndExprReal or TBitwiseOrExprReal or TBitwiseXorExprReal or TBlockArgument or
|
||||
TBlockParameter or TBraceBlock or TBreakStmt or TCaseEqExpr or TCaseExpr or
|
||||
TBlockParameter or TBraceBlockReal or TBreakStmt or TCaseEqExpr or TCaseExpr or
|
||||
TCharacterLiteral or TClassDeclaration or TClassVariableAccessReal or TComplementExpr or
|
||||
TComplexLiteral or TDefinedExpr or TDelimitedSymbolLiteral or TDestructuredLeftAssignment or
|
||||
TDivExprReal or TDo or TDoBlock or TElementReference or TElse or TElsif or TEmptyStmt or
|
||||
TEndBlock or TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or TFloatLiteral or
|
||||
TForExpr or TForIn or TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or
|
||||
TForExpr or TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or
|
||||
TGlobalVariableAccessReal or THashKeySymbolLiteral or THashLiteral or THashSplatExpr or
|
||||
THashSplatParameter or THereDoc or TIdentifierMethodCall or TIf or TIfModifierExpr or
|
||||
TInstanceVariableAccessReal or TIntegerLiteralReal or TKeywordParameter or TLEExpr or
|
||||
@@ -308,7 +311,7 @@ private module Cached {
|
||||
TRegExpMatchExpr or TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or
|
||||
TRegularSuperCall or TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or
|
||||
TScopeResolutionConstantAccess or TScopeResolutionMethodCall or TSelfReal or
|
||||
TSimpleParameter or TSimpleSymbolLiteral or TSingletonClass or TSingletonMethod or
|
||||
TSimpleParameterReal or TSimpleSymbolLiteral or TSingletonClass or TSingletonMethod or
|
||||
TSpaceshipExpr or TSplatExprReal or TSplatParameter or TStringArrayLiteral or
|
||||
TStringConcatenation or TStringEscapeSequenceComponent or TStringInterpolationComponent or
|
||||
TStringTextComponent or TSubExprReal or TSubshellLiteral or TSymbolArrayLiteral or
|
||||
@@ -319,12 +322,13 @@ private module Cached {
|
||||
|
||||
class TAstNodeSynth =
|
||||
TAddExprSynth or TAssignExprSynth or TBitwiseAndExprSynth or TBitwiseOrExprSynth or
|
||||
TBitwiseXorExprSynth or TClassVariableAccessSynth or TConstantReadAccessSynth or
|
||||
TDivExprSynth or TExponentExprSynth or TGlobalVariableAccessSynth or
|
||||
TInstanceVariableAccessSynth or TIntegerLiteralSynth or TLShiftExprSynth or
|
||||
TLocalVariableAccessSynth or TLogicalAndExprSynth or TLogicalOrExprSynth or
|
||||
TMethodCallSynth or TModuloExprSynth or TMulExprSynth or TRShiftExprSynth or
|
||||
TRangeLiteralSynth or TSelfSynth or TSplatExprSynth or TStmtSequenceSynth or TSubExprSynth;
|
||||
TBitwiseXorExprSynth or TBraceBlockSynth or TClassVariableAccessSynth or
|
||||
TConstantReadAccessSynth or TDivExprSynth or TExponentExprSynth or
|
||||
TGlobalVariableAccessSynth or TInstanceVariableAccessSynth or TIntegerLiteralSynth or
|
||||
TLShiftExprSynth or TLocalVariableAccessSynth or TLogicalAndExprSynth or
|
||||
TLogicalOrExprSynth or TMethodCallSynth or TModuloExprSynth or TMulExprSynth or
|
||||
TRShiftExprSynth or TRangeLiteralSynth or TSelfSynth or TSimpleParameterSynth or
|
||||
TSplatExprSynth or TStmtSequenceSynth or TSubExprSynth;
|
||||
|
||||
/**
|
||||
* Gets the underlying TreeSitter entity for a given AST node. This does not
|
||||
@@ -359,7 +363,7 @@ private module Cached {
|
||||
n = TBitwiseXorExprReal(result) or
|
||||
n = TBlockArgument(result) or
|
||||
n = TBlockParameter(result) or
|
||||
n = TBraceBlock(result) or
|
||||
n = TBraceBlockReal(result) or
|
||||
n = TBreakStmt(result) or
|
||||
n = TCaseEqExpr(result) or
|
||||
n = TCaseExpr(result) or
|
||||
@@ -385,7 +389,6 @@ private module Cached {
|
||||
n = TFalseLiteral(result) or
|
||||
n = TFloatLiteral(result) or
|
||||
n = TForExpr(result) or
|
||||
n = TForIn(result) or // TODO REMOVE
|
||||
n = TForwardArgument(result) or
|
||||
n = TForwardParameter(result) or
|
||||
n = TGEExpr(result) or
|
||||
@@ -439,7 +442,7 @@ private module Cached {
|
||||
n = TScopeResolutionConstantAccess(result, _) or
|
||||
n = TScopeResolutionMethodCall(result, _) or
|
||||
n = TSelfReal(result) or
|
||||
n = TSimpleParameter(result) or
|
||||
n = TSimpleParameterReal(result) or
|
||||
n = TSimpleSymbolLiteral(result) or
|
||||
n = TSingletonClass(result) or
|
||||
n = TSingletonMethod(result) or
|
||||
@@ -488,6 +491,8 @@ private module Cached {
|
||||
or
|
||||
result = TBitwiseXorExprSynth(parent, i)
|
||||
or
|
||||
result = TBraceBlockSynth(parent, i)
|
||||
or
|
||||
result = TClassVariableAccessSynth(parent, i, _)
|
||||
or
|
||||
result = TConstantReadAccessSynth(parent, i, _)
|
||||
@@ -522,6 +527,8 @@ private module Cached {
|
||||
or
|
||||
result = TSelfSynth(parent, i, _)
|
||||
or
|
||||
result = TSimpleParameterSynth(parent, i)
|
||||
or
|
||||
result = TSplatExprSynth(parent, i)
|
||||
or
|
||||
result = TStmtSequenceSynth(parent, i)
|
||||
@@ -639,6 +646,8 @@ class TCallable = TMethodBase or TLambda or TBlock;
|
||||
|
||||
class TMethodBase = TMethod or TSingletonMethod;
|
||||
|
||||
class TBraceBlock = TBraceBlockReal or TBraceBlockSynth;
|
||||
|
||||
class TBlock = TDoBlock or TBraceBlock;
|
||||
|
||||
class TModuleBase = TToplevel or TNamespace or TSingletonClass;
|
||||
@@ -730,6 +739,8 @@ class TParameter =
|
||||
TPatternParameter or TBlockParameter or THashSplatParameter or TKeywordParameter or
|
||||
TOptionalParameter or TSplatParameter or TForwardParameter;
|
||||
|
||||
class TSimpleParameter = TSimpleParameterReal or TSimpleParameterSynth;
|
||||
|
||||
class TPatternParameter = TSimpleParameter or TTuplePatternParameter;
|
||||
|
||||
class TNamedParameter =
|
||||
|
||||
@@ -50,7 +50,7 @@ class MethodCallSynth extends MethodCallImpl, TMethodCallSynth {
|
||||
|
||||
final override int getNumberOfArgumentsImpl() { this = TMethodCallSynth(_, _, _, _, result) }
|
||||
|
||||
final override Block getBlockImpl() { none() }
|
||||
final override Block getBlockImpl() { synthChild(this, -2, result) }
|
||||
}
|
||||
|
||||
class IdentifierMethodCall extends MethodCallImpl, TIdentifierMethodCall {
|
||||
|
||||
75
ruby/ql/lib/codeql/ruby/ast/internal/Expr.qll
Normal file
75
ruby/ql/lib/codeql/ruby/ast/internal/Expr.qll
Normal file
@@ -0,0 +1,75 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import AST
|
||||
private import TreeSitter
|
||||
|
||||
class StmtSequenceSynth extends StmtSequence, TStmtSequenceSynth {
|
||||
final override Stmt getStmt(int n) { synthChild(this, n, result) }
|
||||
|
||||
final override string toString() { result = "..." }
|
||||
}
|
||||
|
||||
class Then extends StmtSequence, TThen {
|
||||
private Ruby::Then g;
|
||||
|
||||
Then() { this = TThen(g) }
|
||||
|
||||
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
final override string toString() { result = "then ..." }
|
||||
}
|
||||
|
||||
class Else extends StmtSequence, TElse {
|
||||
private Ruby::Else g;
|
||||
|
||||
Else() { this = TElse(g) }
|
||||
|
||||
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
final override string toString() { result = "else ..." }
|
||||
}
|
||||
|
||||
class Do extends StmtSequence, TDo {
|
||||
private Ruby::Do g;
|
||||
|
||||
Do() { this = TDo(g) }
|
||||
|
||||
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
final override string toString() { result = "do ..." }
|
||||
}
|
||||
|
||||
class Ensure extends StmtSequence, TEnsure {
|
||||
private Ruby::Ensure g;
|
||||
|
||||
Ensure() { this = TEnsure(g) }
|
||||
|
||||
override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
final override string toString() { result = "ensure ..." }
|
||||
}
|
||||
|
||||
// Not defined by dispatch, as it should not be exposed
|
||||
Ruby::AstNode getBodyStmtChild(TBodyStmt b, int i) {
|
||||
result = any(Ruby::Method g | b = TMethod(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::SingletonMethod g | b = TSingletonMethod(g)).getChild(i)
|
||||
or
|
||||
exists(Ruby::Lambda g | b = TLambda(g) |
|
||||
result = g.getBody().(Ruby::DoBlock).getChild(i) or
|
||||
result = g.getBody().(Ruby::Block).getChild(i)
|
||||
)
|
||||
or
|
||||
result = any(Ruby::DoBlock g | b = TDoBlock(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::Program g | b = TToplevel(g)).getChild(i) and
|
||||
not result instanceof Ruby::BeginBlock
|
||||
or
|
||||
result = any(Ruby::Class g | b = TClassDeclaration(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::SingletonClass g | b = TSingletonClass(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::Module g | b = TModuleDeclaration(g)).getChild(i)
|
||||
or
|
||||
result = any(Ruby::Begin g | b = TBeginExpr(g)).getChild(i)
|
||||
}
|
||||
28
ruby/ql/lib/codeql/ruby/ast/internal/Method.qll
Normal file
28
ruby/ql/lib/codeql/ruby/ast/internal/Method.qll
Normal file
@@ -0,0 +1,28 @@
|
||||
private import codeql.ruby.AST
|
||||
private import AST
|
||||
private import TreeSitter
|
||||
|
||||
class BraceBlockReal extends BraceBlock, TBraceBlockReal {
|
||||
private Ruby::Block g;
|
||||
|
||||
BraceBlockReal() { this = TBraceBlockReal(g) }
|
||||
|
||||
final override Parameter getParameter(int n) {
|
||||
toGenerated(result) = g.getParameters().getChild(n)
|
||||
}
|
||||
|
||||
final override Stmt getStmt(int i) { toGenerated(result) = g.getChild(i) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthesized block, such as the block synthesized from the body of
|
||||
* a `for` loop.
|
||||
*/
|
||||
class BraceBlockSynth extends BraceBlock, TBraceBlockSynth {
|
||||
final override Parameter getParameter(int n) { synthChild(this, n, result) }
|
||||
|
||||
final override Stmt getStmt(int i) {
|
||||
i >= 0 and
|
||||
synthChild(this, i + this.getNumberOfParameters(), result)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
private import codeql.ruby.AST
|
||||
private import AST
|
||||
private import TreeSitter
|
||||
private import Variable
|
||||
|
||||
module Parameter {
|
||||
class Range extends Ruby::AstNode {
|
||||
@@ -17,3 +18,29 @@ module Parameter {
|
||||
int getPosition() { result = pos }
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SimpleParameterImpl extends AstNode, TSimpleParameter {
|
||||
abstract LocalVariable getVariableImpl();
|
||||
|
||||
abstract string getNameImpl();
|
||||
}
|
||||
|
||||
class SimpleParameterRealImpl extends SimpleParameterImpl, TSimpleParameterReal {
|
||||
private Ruby::Identifier g;
|
||||
|
||||
SimpleParameterRealImpl() { this = TSimpleParameterReal(g) }
|
||||
|
||||
override LocalVariable getVariableImpl() { result = TLocalVariableReal(_, _, g) }
|
||||
|
||||
override string getNameImpl() { result = g.getValue() }
|
||||
}
|
||||
|
||||
class SimpleParameterSynthImpl extends SimpleParameterImpl, TSimpleParameterSynth {
|
||||
SimpleParameterSynthImpl() { this = TSimpleParameterSynth(_, _) }
|
||||
|
||||
LocalVariableAccessSynth getDefininingAccess() { synthChild(this, 0, result) }
|
||||
|
||||
override LocalVariable getVariableImpl() { result = TLocalVariableSynth(this, _) }
|
||||
|
||||
override string getNameImpl() { result = this.getVariableImpl().getName() }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
private import TreeSitter
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.ast.Scope
|
||||
private import codeql.ruby.ast.internal.AST
|
||||
private import codeql.ruby.ast.internal.Parameter
|
||||
private import codeql.ruby.ast.internal.Variable
|
||||
|
||||
class TScopeType = TMethodBase or TModuleLike or TBlockLike;
|
||||
|
||||
@@ -15,6 +17,8 @@ private class TBlockLike = TDoBlock or TLambda or TBlock or TEndBlock;
|
||||
|
||||
private class TModuleLike = TToplevel or TModuleDeclaration or TClassDeclaration or TSingletonClass;
|
||||
|
||||
private class TScopeReal = TMethodBase or TModuleLike or TDoBlock or TLambda or TBraceBlock;
|
||||
|
||||
module Scope {
|
||||
class TypeRange = Callable::TypeRange or ModuleBase::TypeRange or @ruby_end_block;
|
||||
|
||||
@@ -102,29 +106,105 @@ Ruby::HeredocBody getHereDocBody(Ruby::HeredocBeginning g) {
|
||||
)
|
||||
}
|
||||
|
||||
private Ruby::AstNode specialParentOf(Ruby::AstNode n) {
|
||||
n =
|
||||
[
|
||||
result.(Ruby::Module).getName(), result.(Ruby::Class).getName(),
|
||||
result.(Ruby::Class).getSuperclass(), result.(Ruby::SingletonClass).getValue(),
|
||||
result.(Ruby::Method).getName(), result.(Ruby::SingletonMethod).getName(),
|
||||
result.(Ruby::SingletonMethod).getObject()
|
||||
]
|
||||
}
|
||||
|
||||
private Ruby::AstNode parentOf(Ruby::AstNode n) {
|
||||
n = getHereDocBody(result)
|
||||
or
|
||||
exists(Ruby::AstNode parent | parent = n.getParent() |
|
||||
if
|
||||
n =
|
||||
[
|
||||
parent.(Ruby::Module).getName(), parent.(Ruby::Class).getName(),
|
||||
parent.(Ruby::Class).getSuperclass(), parent.(Ruby::SingletonClass).getValue(),
|
||||
parent.(Ruby::Method).getName(), parent.(Ruby::SingletonMethod).getName(),
|
||||
parent.(Ruby::SingletonMethod).getObject()
|
||||
]
|
||||
then result = parent.getParent()
|
||||
else result = parent
|
||||
)
|
||||
result = specialParentOf(n).getParent()
|
||||
or
|
||||
not exists(specialParentOf(n)) and
|
||||
result = n.getParent()
|
||||
}
|
||||
|
||||
/** Gets the enclosing scope of a node */
|
||||
cached
|
||||
Scope::Range scopeOf(Ruby::AstNode n) {
|
||||
exists(Ruby::AstNode p | p = parentOf(n) |
|
||||
p = result
|
||||
or
|
||||
not p instanceof Scope::Range and result = scopeOf(p)
|
||||
)
|
||||
private AstNode specialParentOfInclSynth(AstNode n) {
|
||||
n =
|
||||
[
|
||||
result.(Namespace).getScopeExpr(), result.(ClassDeclaration).getSuperclassExpr(),
|
||||
result.(SingletonMethod).getObject()
|
||||
]
|
||||
}
|
||||
|
||||
private AstNode parentOfInclSynth(AstNode n) {
|
||||
(
|
||||
result = specialParentOfInclSynth(n).getParent()
|
||||
or
|
||||
not exists(specialParentOfInclSynth(n)) and
|
||||
result = n.getParent()
|
||||
) and
|
||||
(synthChild(_, _, n) implies synthChild(result, _, n))
|
||||
}
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/** Gets the enclosing scope of a node */
|
||||
cached
|
||||
Scope::Range scopeOf(Ruby::AstNode n) {
|
||||
exists(Ruby::AstNode p | p = parentOf(n) |
|
||||
p = result
|
||||
or
|
||||
not p instanceof Scope::Range and result = scopeOf(p)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the enclosing scope of a node. Unlike `scopeOf`, this predicate
|
||||
* operates on the external AST API, and therefore takes synthesized nodes
|
||||
* and synthesized scopes into account.
|
||||
*/
|
||||
cached
|
||||
Scope scopeOfInclSynth(AstNode n) {
|
||||
exists(AstNode p | p = parentOfInclSynth(n) |
|
||||
p = result
|
||||
or
|
||||
not p instanceof Scope and result = scopeOfInclSynth(p)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import Cached
|
||||
|
||||
abstract class ScopeImpl extends AstNode, TScopeType {
|
||||
final Scope getOuterScopeImpl() { result = scopeOfInclSynth(this) }
|
||||
|
||||
abstract Variable getAVariableImpl();
|
||||
|
||||
final Variable getVariableImpl(string name) {
|
||||
result = this.getAVariableImpl() and
|
||||
result.getName() = name
|
||||
}
|
||||
}
|
||||
|
||||
private class ScopeRealImpl extends ScopeImpl, TScopeReal {
|
||||
private Scope::Range range;
|
||||
|
||||
ScopeRealImpl() { range = toGenerated(this) }
|
||||
|
||||
override Variable getAVariableImpl() { result.getDeclaringScope() = this }
|
||||
}
|
||||
|
||||
// We desugar for loops by implementing them as calls to `each` with a block
|
||||
// argument. Though this is how the desugaring is described in the MRI parser,
|
||||
// in practice there is not a real nested scope created, so variables that
|
||||
// may appear to be local to the loop body (e.g. the iteration variable) are
|
||||
// scoped to the outer scope rather than the loop body.
|
||||
private class ScopeSynthImpl extends ScopeImpl, TBraceBlockSynth {
|
||||
ScopeSynthImpl() { this = TBraceBlockSynth(_, _) }
|
||||
|
||||
override Variable getAVariableImpl() {
|
||||
// Synthesized variables introduced as parameters to this scope
|
||||
// As this variable is also synthetic, it is genuinely local to this scope.
|
||||
exists(SimpleParameterSynthImpl p |
|
||||
p = TSimpleParameterSynth(this, _) and
|
||||
p.getVariableImpl() = result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
private import AST
|
||||
private import TreeSitter
|
||||
private import codeql.ruby.ast.internal.Call
|
||||
private import codeql.ruby.ast.internal.Expr
|
||||
private import codeql.ruby.ast.internal.Variable
|
||||
private import codeql.ruby.ast.internal.Pattern
|
||||
private import codeql.ruby.ast.internal.Scope
|
||||
@@ -15,6 +16,7 @@ newtype SynthKind =
|
||||
BitwiseAndExprKind() or
|
||||
BitwiseOrExprKind() or
|
||||
BitwiseXorExprKind() or
|
||||
BraceBlockKind() or
|
||||
ClassVariableAccessKind(ClassVariable v) or
|
||||
DivExprKind() or
|
||||
ExponentExprKind() or
|
||||
@@ -33,6 +35,7 @@ newtype SynthKind =
|
||||
MulExprKind() or
|
||||
RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or
|
||||
RShiftExprKind() or
|
||||
SimpleParameterKind() or
|
||||
SplatExprKind() or
|
||||
StmtSequenceKind() or
|
||||
SelfKind(SelfVariable v) or
|
||||
@@ -814,3 +817,94 @@ private module ArrayLiteralDesugar {
|
||||
final override predicate constantReadAccess(string name) { name = "::Array" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ```rb
|
||||
* for x in xs
|
||||
* <loop_body>
|
||||
* end
|
||||
* ```
|
||||
* desugars to, roughly,
|
||||
* ```rb
|
||||
* xs.each { |__synth__0| x = __synth__0; <loop_body> }
|
||||
* ```
|
||||
*
|
||||
* Note that for-loops, unlike blocks, do not create a new variable scope, so
|
||||
* variables within this block inherit the enclosing scope. The exception to
|
||||
* this is the synthesized variable declared by the block parameter, which is
|
||||
* scoped to the synthesized block.
|
||||
*/
|
||||
private module ForLoopDesugar {
|
||||
pragma[nomagic]
|
||||
private predicate forLoopSynthesis(AstNode parent, int i, Child child) {
|
||||
exists(ForExpr for |
|
||||
// each call
|
||||
parent = for and
|
||||
i = -1 and
|
||||
child = SynthChild(MethodCallKind("each", false, 0))
|
||||
or
|
||||
exists(MethodCall eachCall | eachCall = TMethodCallSynth(for, -1, "each", false, 0) |
|
||||
// receiver
|
||||
parent = eachCall and
|
||||
i = 0 and
|
||||
child = childRef(for.getValue()) // value is the Enumerable
|
||||
or
|
||||
parent = eachCall and
|
||||
i = -2 and
|
||||
child = SynthChild(BraceBlockKind())
|
||||
or
|
||||
exists(Block block | block = TBraceBlockSynth(eachCall, -2) |
|
||||
// block params
|
||||
parent = block and
|
||||
i = 0 and
|
||||
child = SynthChild(SimpleParameterKind())
|
||||
or
|
||||
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
|
||||
parent = param and
|
||||
i = 0 and
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
||||
or
|
||||
// assignment to pattern from for loop to synth parameter
|
||||
parent = block and
|
||||
i = 1 and
|
||||
child = SynthChild(AssignExprKind())
|
||||
or
|
||||
parent = TAssignExprSynth(block, 1) and
|
||||
(
|
||||
i = 0 and
|
||||
child = childRef(for.getPattern())
|
||||
or
|
||||
i = 1 and
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
||||
)
|
||||
)
|
||||
or
|
||||
// rest of block body
|
||||
parent = block and
|
||||
child = childRef(for.getBody().(Do).getStmt(i - 2))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private class ForLoopSynthesis extends Synthesis {
|
||||
final override predicate child(AstNode parent, int i, Child child) {
|
||||
forLoopSynthesis(parent, i, child)
|
||||
}
|
||||
|
||||
final override predicate methodCall(string name, boolean setter, int arity) {
|
||||
name = "each" and
|
||||
setter = false and
|
||||
arity = 0
|
||||
}
|
||||
|
||||
final override predicate localVariable(AstNode n, int i) {
|
||||
n instanceof TSimpleParameterSynth and
|
||||
i = 0
|
||||
}
|
||||
|
||||
final override predicate excludeFromControlFlowTree(AstNode n) {
|
||||
n = any(ForExpr for).getBody()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ private module Cached {
|
||||
|
||||
cached
|
||||
predicate isCapturedAccess(LocalVariableAccess access) {
|
||||
toGenerated(access.getVariable().getDeclaringScope()) != scopeOf(toGenerated(access))
|
||||
access.getVariable().getDeclaringScope() != access.getCfgScope()
|
||||
}
|
||||
|
||||
cached
|
||||
@@ -524,7 +524,7 @@ private class LocalVariableAccessReal extends LocalVariableAccessImpl, TLocalVar
|
||||
final override string toString() { result = g.getValue() }
|
||||
}
|
||||
|
||||
private class LocalVariableAccessSynth extends LocalVariableAccessImpl, TLocalVariableAccessSynth {
|
||||
class LocalVariableAccessSynth extends LocalVariableAccessImpl, TLocalVariableAccessSynth {
|
||||
private LocalVariable v;
|
||||
|
||||
LocalVariableAccessSynth() { this = TLocalVariableAccessSynth(_, _, v) }
|
||||
|
||||
@@ -27,7 +27,7 @@ class CfgScope extends Scope instanceof CfgScope::Range_ {
|
||||
*
|
||||
* Only nodes that can be reached from an entry point are included in the CFG.
|
||||
*/
|
||||
class CfgNode extends TNode {
|
||||
class CfgNode extends TCfgNode {
|
||||
/** Gets a textual representation of this control flow node. */
|
||||
string toString() { none() }
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ private import SuccessorTypes
|
||||
private newtype TCompletion =
|
||||
TSimpleCompletion() or
|
||||
TBooleanCompletion(boolean b) { b in [false, true] } or
|
||||
TEmptinessCompletion(boolean isEmpty) { isEmpty in [false, true] } or
|
||||
TMatchingCompletion(boolean isMatch) { isMatch in [false, true] } or
|
||||
TReturnCompletion() or
|
||||
TBreakCompletion() or
|
||||
@@ -54,9 +53,6 @@ private predicate nestedEnsureCompletion(Completion outer, int nestLevel) {
|
||||
|
||||
pragma[noinline]
|
||||
private predicate completionIsValidForStmt(AstNode n, Completion c) {
|
||||
n = TForIn(_) and
|
||||
c instanceof EmptinessCompletion
|
||||
or
|
||||
n instanceof BreakStmt and
|
||||
c = TBreakCompletion()
|
||||
or
|
||||
@@ -242,8 +238,8 @@ class SimpleCompletion extends NonNestedNormalCompletion, TSimpleCompletion {
|
||||
|
||||
/**
|
||||
* A completion that represents evaluation of an expression, whose value determines
|
||||
* the successor. Either a Boolean completion (`BooleanCompletion`), an emptiness
|
||||
* completion (`EmptinessCompletion`), or a matching completion (`MatchingCompletion`).
|
||||
* the successor. Either a Boolean completion (`BooleanCompletion`), or a matching
|
||||
* completion (`MatchingCompletion`).
|
||||
*/
|
||||
abstract class ConditionalCompletion extends NonNestedNormalCompletion {
|
||||
boolean value;
|
||||
@@ -280,18 +276,6 @@ class FalseCompletion extends BooleanCompletion {
|
||||
FalseCompletion() { this.getValue() = false }
|
||||
}
|
||||
|
||||
/**
|
||||
* A completion that represents evaluation of an emptiness test, for example
|
||||
* a test in a `for in` statement.
|
||||
*/
|
||||
class EmptinessCompletion extends ConditionalCompletion, TEmptinessCompletion {
|
||||
EmptinessCompletion() { this = TEmptinessCompletion(value) }
|
||||
|
||||
override EmptinessSuccessor getAMatchingSuccessorType() { result.getValue() = value }
|
||||
|
||||
override string toString() { if value = true then result = "empty" else result = "non-empty" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A completion that represents evaluation of a matching test, for example
|
||||
* a test in a `rescue` statement.
|
||||
|
||||
@@ -61,6 +61,9 @@ module CfgScope {
|
||||
|
||||
final override predicate exit(AstNode last, Completion c) {
|
||||
last(this.(Trees::EndBlockTree).getLastBodyChild(), last, c)
|
||||
or
|
||||
last(this.(Trees::EndBlockTree).getBodyChild(_, _), last, c) and
|
||||
not c instanceof NormalCompletion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +82,9 @@ module CfgScope {
|
||||
|
||||
final override predicate exit(AstNode last, Completion c) {
|
||||
last(this.(Trees::BraceBlockTree).getLastBodyChild(), last, c)
|
||||
or
|
||||
last(this.(Trees::BraceBlockTree).getBodyChild(_, _), last, c) and
|
||||
not c instanceof NormalCompletion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,25 +97,6 @@ predicate succEntry(CfgScope::Range_ scope, AstNode first) { scope.entry(first)
|
||||
pragma[nomagic]
|
||||
predicate succExit(CfgScope::Range_ scope, AstNode last, Completion c) { scope.exit(last, c) }
|
||||
|
||||
// TODO: remove this class; it should be replaced with an implicit non AST node
|
||||
private class ForIn extends AstNode, ASTInternal::TForIn {
|
||||
final override string toString() { result = "In" }
|
||||
}
|
||||
|
||||
// TODO: remove this class; it should be replaced with an implicit non AST node
|
||||
private class ForRange extends ForExpr {
|
||||
override AstNode getAChild(string pred) {
|
||||
result = super.getAChild(pred)
|
||||
or
|
||||
pred = "<in>" and
|
||||
result = this.getIn()
|
||||
}
|
||||
|
||||
ForIn getIn() {
|
||||
result = ASTInternal::TForIn(ASTInternal::toGenerated(this).(Ruby::For).getValue())
|
||||
}
|
||||
}
|
||||
|
||||
/** Defines the CFG by dispatch on the various AST types. */
|
||||
module Trees {
|
||||
private class AliasStmtTree extends StandardPreOrderTree, AliasStmt {
|
||||
@@ -610,89 +597,6 @@ module Trees {
|
||||
|
||||
private class ForwardParameterTree extends LeafTree, ForwardParameter { }
|
||||
|
||||
private class ForInTree extends LeafTree, ForIn { }
|
||||
|
||||
/**
|
||||
* Control flow of a for-in loop
|
||||
*
|
||||
* For example, this program fragment:
|
||||
*
|
||||
* ```rb
|
||||
* for arg in args do
|
||||
* puts arg
|
||||
* end
|
||||
* puts "done";
|
||||
* ```
|
||||
*
|
||||
* has the following control flow graph:
|
||||
*
|
||||
* ```
|
||||
* args
|
||||
* |
|
||||
* in------<-----
|
||||
* / \ \
|
||||
* / \ |
|
||||
* / \ |
|
||||
* / \ |
|
||||
* empty non-empty |
|
||||
* | \ |
|
||||
* for \ |
|
||||
* | arg |
|
||||
* | | |
|
||||
* puts "done" puts arg |
|
||||
* \___/
|
||||
* ```
|
||||
*/
|
||||
private class ForTree extends PostOrderTree, ForRange {
|
||||
final override predicate propagatesAbnormal(AstNode child) {
|
||||
child = this.getPattern() or child = this.getValue()
|
||||
}
|
||||
|
||||
final override predicate first(AstNode first) { first(this.getValue(), first) }
|
||||
|
||||
/**
|
||||
* for pattern in array do body end
|
||||
* ```
|
||||
* array +-> in +--[non empty]--> pattern -> body -> in
|
||||
* |--[empty]--> for
|
||||
* ```
|
||||
*/
|
||||
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
|
||||
last(this.getValue(), pred, c) and
|
||||
first(this.getIn(), succ) and
|
||||
c instanceof SimpleCompletion
|
||||
or
|
||||
last(this.getIn(), pred, c) and
|
||||
first(this.getPattern(), succ) and
|
||||
c.(EmptinessCompletion).getValue() = false
|
||||
or
|
||||
last(this.getPattern(), pred, c) and
|
||||
first(this.getBody(), succ) and
|
||||
c instanceof NormalCompletion
|
||||
or
|
||||
last(this.getBody(), pred, c) and
|
||||
first(this.getIn(), succ) and
|
||||
c.continuesLoop()
|
||||
or
|
||||
last(this.getBody(), pred, c) and
|
||||
first(this.getBody(), succ) and
|
||||
c instanceof RedoCompletion
|
||||
or
|
||||
succ = this and
|
||||
(
|
||||
last(this.getIn(), pred, c) and
|
||||
c.(EmptinessCompletion).getValue() = true
|
||||
or
|
||||
last(this.getBody(), pred, c) and
|
||||
not c.continuesLoop() and
|
||||
not c instanceof BreakCompletion and
|
||||
not c instanceof RedoCompletion
|
||||
or
|
||||
last(this.getBody(), pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class GlobalVariableTree extends LeafTree, GlobalVariableAccess { }
|
||||
|
||||
private class HashLiteralTree extends StandardPostOrderTree, HashLiteral {
|
||||
@@ -1137,6 +1041,9 @@ private Scope parent(Scope n) {
|
||||
not n instanceof CfgScope::Range_
|
||||
}
|
||||
|
||||
cached
|
||||
private CfgScope getCfgScopeImpl(AstNode n) { result = parent*(scopeOfInclSynth(n)) }
|
||||
|
||||
/** Gets the CFG scope of node `n`. */
|
||||
pragma[inline]
|
||||
CfgScope getCfgScope(AstNode n) {
|
||||
@@ -1148,13 +1055,6 @@ CfgScope getCfgScope(AstNode n) {
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/** Gets the CFG scope of node `n`. */
|
||||
cached
|
||||
CfgScope getCfgScopeImpl(AstNode n) {
|
||||
forceCachingInSameStage() and
|
||||
result = parent*(ASTInternal::fromGenerated(scopeOf(ASTInternal::toGeneratedInclSynth(n))))
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TSuccessorType =
|
||||
TSuccessorSuccessor() or
|
||||
|
||||
@@ -788,7 +788,7 @@ private module Cached {
|
||||
* The control flow graph is pruned for unreachable nodes.
|
||||
*/
|
||||
cached
|
||||
newtype TNode =
|
||||
newtype TCfgNode =
|
||||
TEntryNode(CfgScope scope) { succEntrySplits(scope, _, _, _) } or
|
||||
TAnnotatedExitNode(CfgScope scope, boolean normal) {
|
||||
exists(Reachability::SameSplitsBlock b, SuccessorType t | b.isReachable(_) |
|
||||
@@ -807,7 +807,7 @@ private module Cached {
|
||||
|
||||
/** Gets a successor node of a given flow type, if any. */
|
||||
cached
|
||||
TNode getASuccessor(TNode pred, SuccessorType t) {
|
||||
TCfgNode getASuccessor(TCfgNode pred, SuccessorType t) {
|
||||
// Callable entry node -> callable body
|
||||
exists(ControlFlowElement succElement, Splits succSplits, CfgScope scope |
|
||||
result = TElementNode(succElement, succSplits) and
|
||||
|
||||
@@ -139,6 +139,18 @@ module EnsureSplitting {
|
||||
|
||||
/** Holds if this node is the entry node in the `ensure` block it belongs to. */
|
||||
predicate isEntryNode() { first(block.getEnsure(), this) }
|
||||
|
||||
BodyStmt getBlock() { result = block }
|
||||
|
||||
pragma[noinline]
|
||||
predicate isEntered(AstNode pred, int nestLevel, Completion c) {
|
||||
this.isEntryNode() and
|
||||
nestLevel = this.getNestLevel() and
|
||||
succ(pred, this, c) and
|
||||
// the entry node may be reachable via a backwards loop edge; in this case
|
||||
// the split has already been entered
|
||||
not pred = block.getAnEnsureDescendant()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,18 +217,11 @@ module EnsureSplitting {
|
||||
override string toString() { result = "ensure (" + nestLevel + ")" }
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasEntry0(AstNode pred, EnsureNode succ, int nestLevel, Completion c) {
|
||||
succ.isEntryNode() and
|
||||
nestLevel = succ.getNestLevel() and
|
||||
succ(pred, succ, c)
|
||||
}
|
||||
|
||||
private class EnsureSplitImpl extends SplitImpl, EnsureSplit {
|
||||
override EnsureSplitKind getKind() { result.getNestLevel() = this.getNestLevel() }
|
||||
|
||||
override predicate hasEntry(AstNode pred, AstNode succ, Completion c) {
|
||||
hasEntry0(pred, succ, this.getNestLevel(), c) and
|
||||
succ.(EnsureNode).isEntered(pred, this.getNestLevel(), c) and
|
||||
this.getType().isSplitForEntryCompletion(c)
|
||||
}
|
||||
|
||||
@@ -242,8 +247,8 @@ module EnsureSplitting {
|
||||
* `inherited` indicates whether `c` is an inherited completion from the
|
||||
* body.
|
||||
*/
|
||||
private predicate exit(Trees::BodyStmtTree block, AstNode pred, Completion c, boolean inherited) {
|
||||
exists(EnsureSplitType type |
|
||||
private predicate exit(AstNode pred, Completion c, boolean inherited) {
|
||||
exists(Trees::BodyStmtTree block, EnsureSplitType type |
|
||||
this.exit0(pred, block, this.getNestLevel(), c) and
|
||||
type = this.getType()
|
||||
|
|
||||
@@ -294,7 +299,7 @@ module EnsureSplitting {
|
||||
this.appliesToPredecessor(pred) and
|
||||
exists(EnsureSplitImpl outer |
|
||||
outer.getNestLevel() = this.getNestLevel() - 1 and
|
||||
outer.exit(_, pred, c, inherited) and
|
||||
outer.exit(pred, c, inherited) and
|
||||
this.getType() instanceof NormalSuccessor and
|
||||
inherited = true
|
||||
)
|
||||
@@ -303,18 +308,18 @@ module EnsureSplitting {
|
||||
override predicate hasExit(AstNode pred, AstNode succ, Completion c) {
|
||||
succ(pred, succ, c) and
|
||||
(
|
||||
this.exit(_, pred, c, _)
|
||||
this.exit(pred, c, _)
|
||||
or
|
||||
this.exit(_, pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
|
||||
this.exit(pred, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasExitScope(CfgScope scope, AstNode last, Completion c) {
|
||||
succExit(scope, last, c) and
|
||||
(
|
||||
this.exit(_, last, c, _)
|
||||
this.exit(last, c, _)
|
||||
or
|
||||
this.exit(_, last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
|
||||
this.exit(last, c.(NestedBreakCompletion).getAnInnerCompatibleCompletion(), _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -323,7 +328,7 @@ module EnsureSplitting {
|
||||
succ(pred, succ, c) and
|
||||
succ =
|
||||
any(EnsureNode en |
|
||||
if en.isEntryNode()
|
||||
if en.isEntryNode() and en.getBlock() != pred.(EnsureNode).getBlock()
|
||||
then
|
||||
// entering a nested `ensure` block
|
||||
en.getNestLevel() > this.getNestLevel()
|
||||
|
||||
@@ -64,37 +64,37 @@ module LocalFlow {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a local use-use flow step from `nodeFrom` to `nodeTo`
|
||||
* involving SSA definition `def`.
|
||||
*/
|
||||
predicate localSsaFlowStepUseUse(Ssa::Definition def, Node nodeFrom, Node nodeTo) {
|
||||
def.hasAdjacentReads(nodeFrom.asExpr(), nodeTo.asExpr())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
|
||||
* SSA definition `def`.
|
||||
*/
|
||||
predicate localSsaFlowStep(Ssa::Definition def, Node nodeFrom, Node nodeTo) {
|
||||
// Flow from assignment into SSA definition
|
||||
def.(Ssa::WriteDefinition).assigns(nodeFrom.asExpr()) and
|
||||
nodeTo.(SsaDefinitionNode).getDefinition() = def
|
||||
or
|
||||
// Flow from SSA definition to first read
|
||||
def = nodeFrom.(SsaDefinitionNode).getDefinition() and
|
||||
nodeTo.asExpr() = def.getAFirstRead()
|
||||
or
|
||||
// Flow from read to next read
|
||||
exists(
|
||||
CfgNodes::ExprNodes::VariableReadAccessCfgNode read1,
|
||||
CfgNodes::ExprNodes::VariableReadAccessCfgNode read2
|
||||
|
|
||||
def.hasAdjacentReads(read1, read2) and
|
||||
nodeTo.asExpr() = read2
|
||||
|
|
||||
nodeFrom.asExpr() = read1
|
||||
private predicate localSsaFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(Ssa::Definition def |
|
||||
// Flow from assignment into SSA definition
|
||||
def.(Ssa::WriteDefinition).assigns(nodeFrom.asExpr()) and
|
||||
nodeTo.(SsaDefinitionNode).getDefinition() = def
|
||||
or
|
||||
read1 = nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()
|
||||
)
|
||||
or
|
||||
// Flow into phi node
|
||||
exists(Ssa::PhiNode phi |
|
||||
localFlowSsaInput(nodeFrom, def, phi) and
|
||||
phi = nodeTo.(SsaDefinitionNode).getDefinition() and
|
||||
def = phi.getAnInput()
|
||||
// Flow from SSA definition to first read
|
||||
def = nodeFrom.(SsaDefinitionNode).getDefinition() and
|
||||
nodeTo.asExpr() = def.getAFirstRead()
|
||||
or
|
||||
// Flow from read to next read
|
||||
localSsaFlowStepUseUse(def, nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
|
||||
or
|
||||
// Flow into phi node
|
||||
exists(Ssa::PhiNode phi |
|
||||
localFlowSsaInput(nodeFrom, def, phi) and
|
||||
phi = nodeTo.(SsaDefinitionNode).getDefinition() and
|
||||
def = phi.getAnInput()
|
||||
)
|
||||
)
|
||||
// TODO
|
||||
// or
|
||||
@@ -105,6 +105,42 @@ module LocalFlow {
|
||||
// def = uncertain.getPriorDefinition()
|
||||
// )
|
||||
}
|
||||
|
||||
predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
|
||||
localSsaFlowStep(nodeFrom, nodeTo)
|
||||
or
|
||||
nodeFrom.(SelfParameterNode).getMethod() = nodeTo.asExpr().getExpr().getEnclosingCallable() and
|
||||
nodeTo.asExpr().getExpr() instanceof Self
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::AssignExprCfgNode).getRhs()
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::BlockArgumentCfgNode).getValue()
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::StmtSequenceCfgNode).getLastStmt()
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ConditionalExprCfgNode).getBranch(_)
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::CaseExprCfgNode).getBranch(_)
|
||||
or
|
||||
exists(CfgNodes::ExprCfgNode exprTo, ReturningStatementNode n |
|
||||
nodeFrom = n and
|
||||
exprTo = nodeTo.asExpr() and
|
||||
n.getReturningNode().getNode() instanceof BreakStmt and
|
||||
exprTo.getNode() instanceof Loop and
|
||||
nodeTo.asExpr().getAPredecessor(any(SuccessorTypes::BreakSuccessor s)) = n.getReturningNode()
|
||||
)
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.(ReturningStatementNode).getReturningNode().getReturnedValueNode()
|
||||
or
|
||||
nodeTo.asExpr() =
|
||||
any(CfgNodes::ExprNodes::ForExprCfgNode for |
|
||||
exists(SuccessorType s |
|
||||
not s instanceof SuccessorTypes::BreakSuccessor and
|
||||
exists(for.getAPredecessor(s))
|
||||
) and
|
||||
nodeFrom.asExpr() = for.getValue()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An argument of a call (including qualifier arguments, excluding block arguments). */
|
||||
@@ -160,49 +196,13 @@ private module Cached {
|
||||
p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr()
|
||||
}
|
||||
|
||||
private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localSsaFlowStep(_, nodeFrom, nodeTo)
|
||||
or
|
||||
nodeFrom.(SelfParameterNode).getMethod() = nodeTo.asExpr().getExpr().getEnclosingCallable() and
|
||||
nodeTo.asExpr().getExpr() instanceof Self
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::AssignExprCfgNode).getRhs()
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::BlockArgumentCfgNode).getValue()
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::StmtSequenceCfgNode).getLastStmt()
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ConditionalExprCfgNode).getBranch(_)
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::CaseExprCfgNode).getBranch(_)
|
||||
or
|
||||
exists(CfgNodes::ExprCfgNode exprTo, ReturningStatementNode n |
|
||||
nodeFrom = n and
|
||||
exprTo = nodeTo.asExpr() and
|
||||
n.getReturningNode().getNode() instanceof BreakStmt and
|
||||
exprTo.getNode() instanceof Loop and
|
||||
nodeTo.asExpr().getAPredecessor(any(SuccessorTypes::BreakSuccessor s)) = n.getReturningNode()
|
||||
)
|
||||
or
|
||||
nodeFrom.asExpr() = nodeTo.(ReturningStatementNode).getReturningNode().getReturnedValueNode()
|
||||
or
|
||||
nodeTo.asExpr() =
|
||||
any(CfgNodes::ExprNodes::ForExprCfgNode for |
|
||||
exists(SuccessorType s |
|
||||
not s instanceof SuccessorTypes::BreakSuccessor and
|
||||
exists(for.getAPredecessor(s))
|
||||
) and
|
||||
nodeFrom.asExpr() = for.getValue()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the local flow predicate that is used as a building block in global
|
||||
* data flow.
|
||||
*/
|
||||
cached
|
||||
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
localFlowStepCommon(nodeFrom, nodeTo)
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
|
||||
or
|
||||
@@ -210,18 +210,23 @@ private module Cached {
|
||||
or
|
||||
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and
|
||||
not FlowSummaryImpl::Private::Steps::summaryClearsContentArg(nodeFrom, _)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
|
||||
}
|
||||
|
||||
/** This is the local flow predicate that is exposed. */
|
||||
cached
|
||||
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
|
||||
localFlowStepCommon(nodeFrom, nodeTo)
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
|
||||
or
|
||||
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
|
||||
or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
// step relation, even though flow is technically inter-procedural
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, true)
|
||||
@@ -230,12 +235,14 @@ private module Cached {
|
||||
/** This is the local flow predicate that is used in type tracking. */
|
||||
cached
|
||||
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
|
||||
localFlowStepCommon(nodeFrom, nodeTo)
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
exists(NamedParameter p |
|
||||
defaultValueFlow(p, nodeFrom) and
|
||||
nodeTo = LocalFlow::getParameterDefNode(p)
|
||||
)
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
cached
|
||||
|
||||
@@ -645,17 +645,18 @@ module Consistency {
|
||||
|
||||
query predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) {
|
||||
ssaDefReachesRead(v, def, bb, i) and
|
||||
not exists(unique(Definition def0 | ssaDefReachesRead(_, def0, bb, i)))
|
||||
not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i)))
|
||||
}
|
||||
|
||||
query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) {
|
||||
variableRead(bb, i, v, _) and
|
||||
not ssaDefReachesRead(_, _, bb, i)
|
||||
not ssaDefReachesRead(v, _, bb, i)
|
||||
}
|
||||
|
||||
query predicate deadDef(RelevantDefinition def, SourceVariable v) {
|
||||
v = def.getSourceVariable() and
|
||||
not ssaDefReachesRead(_, def, _, _) and
|
||||
not phiHasInputFromBlock(_, def, _)
|
||||
not phiHasInputFromBlock(_, def, _) and
|
||||
not uncertainWriteDefinitionInput(_, def)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,3 +257,21 @@ predicate controllerTemplateFile(ActionControllerControllerClass cls, ErbFile te
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to either `skip_forgery_protection` or
|
||||
* `skip_before_action :verify_authenticity_token` to disable CSRF authenticity
|
||||
* token protection.
|
||||
*/
|
||||
class ActionControllerSkipForgeryProtectionCall extends CSRFProtectionSetting::Range {
|
||||
ActionControllerSkipForgeryProtectionCall() {
|
||||
exists(MethodCall call | call = this.asExpr().getExpr() |
|
||||
call.getMethodName() = "skip_forgery_protection"
|
||||
or
|
||||
call.getMethodName() = "skip_before_action" and
|
||||
call.getAnArgument().(SymbolLiteral).getValueText() = "verify_authenticity_token"
|
||||
)
|
||||
}
|
||||
|
||||
override boolean getVerificationSetting() { result = false }
|
||||
}
|
||||
|
||||
131
ruby/ql/lib/codeql/ruby/frameworks/Rails.qll
Normal file
131
ruby/ql/lib/codeql/ruby/frameworks/Rails.qll
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Provides classes for working with Rails.
|
||||
*/
|
||||
|
||||
private import codeql.files.FileSystem
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActiveStorage
|
||||
private import codeql.ruby.ast.internal.Module
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* A reference to either `Rails::Railtie`, `Rails::Engine`, or `Rails::Application`.
|
||||
* `Engine` and `Application` extend `Railtie`, but may not have definitions present in the database.
|
||||
*/
|
||||
private class RailtieClassAccess extends ConstantReadAccess {
|
||||
RailtieClassAccess() {
|
||||
this.getScopeExpr().(ConstantAccess).getName() = "Rails" and
|
||||
this.getName() = ["Railtie", "Engine", "Application"]
|
||||
}
|
||||
}
|
||||
|
||||
// A `ClassDeclaration` that (transitively) extends `Rails::Railtie`
|
||||
private class RailtieClass extends ClassDeclaration {
|
||||
RailtieClass() {
|
||||
this.getSuperclassExpr() instanceof RailtieClassAccess or
|
||||
exists(RailtieClass other | other.getModule() = resolveScopeExpr(this.getSuperclassExpr()))
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlow::CallNode getAConfigureCallNode() {
|
||||
// `Rails.application.configure`
|
||||
result = API::getTopLevelMember("Rails").getReturn("application").getAMethodCall("configure")
|
||||
or
|
||||
// `Rails::Application.configure`
|
||||
exists(ConstantReadAccess read, RailtieClass cls |
|
||||
read = result.getReceiver().asExpr().getExpr() and
|
||||
resolveScopeExpr(read) = cls.getModule() and
|
||||
result.asExpr().getExpr().(MethodCall).getMethodName() = "configure"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a Rails config object.
|
||||
*/
|
||||
private class ConfigSourceNode extends DataFlow::LocalSourceNode {
|
||||
ConfigSourceNode() {
|
||||
// `Foo < Rails::Application ... config ...`
|
||||
exists(MethodCall configCall | this.asExpr().getExpr() = configCall |
|
||||
configCall.getMethodName() = "config" and
|
||||
configCall.getEnclosingModule() instanceof RailtieClass
|
||||
)
|
||||
or
|
||||
// `Rails.application.config`
|
||||
this =
|
||||
API::getTopLevelMember("Rails")
|
||||
.getReturn("application")
|
||||
.getReturn("config")
|
||||
.getAnImmediateUse()
|
||||
or
|
||||
// `Rails.application.configure { ... config ... }`
|
||||
// `Rails::Application.configure { ... config ... }`
|
||||
exists(DataFlow::CallNode configureCallNode, Block block, MethodCall configCall |
|
||||
configCall = this.asExpr().getExpr()
|
||||
|
|
||||
configureCallNode = getAConfigureCallNode() and
|
||||
block = configureCallNode.asExpr().getExpr().(MethodCall).getBlock() and
|
||||
configCall.getParent+() = block and
|
||||
configCall.getMethodName() = "config"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ConfigNode extends DataFlow::Node {
|
||||
ConfigNode() { exists(ConfigSourceNode src | src.flowsTo(this)) }
|
||||
}
|
||||
|
||||
// A call where the Rails application config is the receiver
|
||||
private class CallAgainstConfig extends DataFlow::CallNode {
|
||||
CallAgainstConfig() { this.getReceiver() instanceof ConfigNode }
|
||||
|
||||
MethodCall getCall() { result = this.asExpr().getExpr() }
|
||||
|
||||
Block getBlock() { result = this.getCall().getBlock() }
|
||||
}
|
||||
|
||||
private class ActionControllerConfigNode extends DataFlow::Node {
|
||||
ActionControllerConfigNode() {
|
||||
exists(CallAgainstConfig source | source.getCall().getMethodName() = "action_controller" |
|
||||
source.flowsTo(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `node` can contain `value`. */
|
||||
private predicate hasBooleanValue(DataFlow::Node node, boolean value) {
|
||||
exists(DataFlow::LocalSourceNode literal |
|
||||
literal.asExpr().getExpr().(BooleanLiteral).getValue() = value and
|
||||
literal.flowsTo(node)
|
||||
)
|
||||
}
|
||||
|
||||
// `<actionControllerConfig>.allow_forgery_protection = <verificationSetting>`
|
||||
private DataFlow::CallNode getAnAllowForgeryProtectionCall(boolean verificationSetting) {
|
||||
// exclude some test configuration
|
||||
not (
|
||||
result.getLocation().getFile().getRelativePath().matches("%test/%") or
|
||||
result.getLocation().getFile().getStem() = "test"
|
||||
) and
|
||||
result.getReceiver() instanceof ActionControllerConfigNode and
|
||||
result.asExpr().getExpr().(MethodCall).getMethodName() = "allow_forgery_protection=" and
|
||||
hasBooleanValue(result.getArgument(0), verificationSetting)
|
||||
}
|
||||
|
||||
/**
|
||||
* A `DataFlow::Node` that may enable or disable Rails CSRF protection in
|
||||
* production code.
|
||||
*/
|
||||
private class AllowForgeryProtectionSetting extends CSRFProtectionSetting::Range {
|
||||
private boolean verificationSetting;
|
||||
|
||||
AllowForgeryProtectionSetting() { this = getAnAllowForgeryProtectionCall(verificationSetting) }
|
||||
|
||||
override boolean getVerificationSetting() { result = verificationSetting }
|
||||
}
|
||||
// TODO: initialization hooks, e.g. before_configuration, after_initialize...
|
||||
// TODO: initializers
|
||||
Reference in New Issue
Block a user