mirror of
https://github.com/github/codeql.git
synced 2026-05-04 05:05:12 +02:00
Merge remote-tracking branch 'origin/main' into nickrolfe/regex_injection
This commit is contained in:
@@ -252,8 +252,8 @@ module API {
|
||||
* Holds if `ref` is a use of a node that should have an incoming edge from the root
|
||||
* node labeled `lbl` in the API graph.
|
||||
*/
|
||||
cached
|
||||
predicate useRoot(string lbl, DataFlow::Node ref) {
|
||||
pragma[nomagic]
|
||||
private predicate useRoot(string lbl, DataFlow::Node ref) {
|
||||
exists(string name, ExprNodes::ConstantAccessCfgNode access, ConstantReadAccess read |
|
||||
access = ref.asExpr() and
|
||||
lbl = Label::member(read.getName()) and
|
||||
@@ -268,49 +268,37 @@ module API {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` is a use of a node that should have an incoming edge from use node
|
||||
* `base` labeled `lbl` in the API graph.
|
||||
* Holds if `ref` is a use of a node that should have an incoming edge labeled `lbl`,
|
||||
* from a use node that flows to `node`.
|
||||
*/
|
||||
cached
|
||||
predicate useUse(DataFlow::LocalSourceNode base, string lbl, DataFlow::Node ref) {
|
||||
exists(ExprCfgNode node |
|
||||
// First, we find a predecessor of the node `ref` that we want to determine. The predecessor
|
||||
// is any node that is a type-tracked use of a data flow node (`src`), which is itself a
|
||||
// reference to the API node `base`. Thus, `pred` and `src` both represent uses of `base`.
|
||||
//
|
||||
// Once we have identified the predecessor, we define its relation to the successor `ref` as
|
||||
// well as the label on the edge from `pred` to `ref`. This label describes the nature of
|
||||
// the relationship between `pred` and `ref`.
|
||||
useExpr(node, base)
|
||||
|
|
||||
// // Referring to an attribute on a node that is a use of `base`:
|
||||
// pred = `Rails` part of `Rails::Whatever`
|
||||
// lbl = `Whatever`
|
||||
// ref = `Rails::Whatever`
|
||||
exists(ExprNodes::ConstantAccessCfgNode c, ConstantReadAccess read |
|
||||
not exists(resolveTopLevel(read)) and
|
||||
node = c.getScopeExpr() and
|
||||
lbl = Label::member(read.getName()) and
|
||||
ref.asExpr() = c and
|
||||
read = c.getExpr()
|
||||
)
|
||||
or
|
||||
// Calling a method on a node that is a use of `base`
|
||||
exists(ExprNodes::MethodCallCfgNode call, string name |
|
||||
node = call.getReceiver() and
|
||||
name = call.getExpr().getMethodName() and
|
||||
lbl = Label::return(name) and
|
||||
name != "new" and
|
||||
ref.asExpr() = call
|
||||
)
|
||||
or
|
||||
// Calling the `new` method on a node that is a use of `base`, which creates a new instance
|
||||
exists(ExprNodes::MethodCallCfgNode call |
|
||||
node = call.getReceiver() and
|
||||
lbl = Label::instance() and
|
||||
call.getExpr().getMethodName() = "new" and
|
||||
ref.asExpr() = call
|
||||
)
|
||||
private predicate useStep(string lbl, ExprCfgNode node, DataFlow::Node ref) {
|
||||
// // Referring to an attribute on a node that is a use of `base`:
|
||||
// pred = `Rails` part of `Rails::Whatever`
|
||||
// lbl = `Whatever`
|
||||
// ref = `Rails::Whatever`
|
||||
exists(ExprNodes::ConstantAccessCfgNode c, ConstantReadAccess read |
|
||||
not exists(resolveTopLevel(read)) and
|
||||
node = c.getScopeExpr() and
|
||||
lbl = Label::member(read.getName()) and
|
||||
ref.asExpr() = c and
|
||||
read = c.getExpr()
|
||||
)
|
||||
or
|
||||
// Calling a method on a node that is a use of `base`
|
||||
exists(ExprNodes::MethodCallCfgNode call, string name |
|
||||
node = call.getReceiver() and
|
||||
name = call.getExpr().getMethodName() and
|
||||
lbl = Label::return(name) and
|
||||
name != "new" and
|
||||
ref.asExpr() = call
|
||||
)
|
||||
or
|
||||
// Calling the `new` method on a node that is a use of `base`, which creates a new instance
|
||||
exists(ExprNodes::MethodCallCfgNode call |
|
||||
node = call.getReceiver() and
|
||||
lbl = Label::instance() and
|
||||
call.getExpr().getMethodName() = "new" and
|
||||
ref.asExpr() = call
|
||||
)
|
||||
}
|
||||
|
||||
@@ -318,14 +306,10 @@ module API {
|
||||
private predicate isUse(DataFlow::Node nd) {
|
||||
useRoot(_, nd)
|
||||
or
|
||||
useUse(_, _, nd)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate useExpr(ExprCfgNode node, DataFlow::LocalSourceNode src) {
|
||||
exists(DataFlow::LocalSourceNode pred |
|
||||
pred = trackUseNode(src) and
|
||||
pred.flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node))
|
||||
exists(ExprCfgNode node, DataFlow::LocalSourceNode pred |
|
||||
pred = useCandFwd() and
|
||||
pred.flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) and
|
||||
useStep(_, node, nd)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -335,26 +319,54 @@ module API {
|
||||
cached
|
||||
predicate use(TApiNode nd, DataFlow::Node ref) { nd = MkUse(ref) }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
|
||||
*
|
||||
* The flow from `src` to that node may be inter-procedural.
|
||||
*/
|
||||
private DataFlow::LocalSourceNode trackUseNode(DataFlow::Node src, TypeTracker t) {
|
||||
// Declaring `src` to be a `LocalSourceNode` currently causes a redundant check in the
|
||||
// recursive case, so instead we check it explicitly here.
|
||||
src instanceof DataFlow::LocalSourceNode and
|
||||
private DataFlow::LocalSourceNode useCandFwd(TypeTracker t) {
|
||||
t.start() and
|
||||
isUse(src) and
|
||||
result = src
|
||||
isUse(result)
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t))
|
||||
exists(TypeTracker t2 | result = useCandFwd(t2).track(t2, t))
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode useCandFwd() { result = useCandFwd(TypeTracker::end()) }
|
||||
|
||||
private DataFlow::Node useCandRev(TypeBackTracker tb) {
|
||||
result = useCandFwd() and
|
||||
tb.start()
|
||||
or
|
||||
exists(TypeBackTracker tb2, DataFlow::LocalSourceNode mid, TypeTracker t |
|
||||
mid = useCandRev(tb2) and
|
||||
result = mid.backtrack(tb2, tb) and
|
||||
pragma[only_bind_out](result) = useCandFwd(t) and
|
||||
pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
|
||||
)
|
||||
}
|
||||
|
||||
private DataFlow::LocalSourceNode useCandRev() {
|
||||
result = useCandRev(TypeBackTracker::end()) and
|
||||
isUse(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
|
||||
*
|
||||
* The flow from `src` to that node may be inter-procedural.
|
||||
* The flow from `src` to the returned node may be inter-procedural.
|
||||
*/
|
||||
private DataFlow::Node trackUseNode(DataFlow::LocalSourceNode src, TypeTracker t) {
|
||||
result = src and
|
||||
result = useCandRev() and
|
||||
t.start()
|
||||
or
|
||||
exists(TypeTracker t2, DataFlow::LocalSourceNode mid, TypeBackTracker tb |
|
||||
mid = trackUseNode(src, t2) and
|
||||
result = mid.track(t2, t) and
|
||||
pragma[only_bind_out](result) = useCandRev(tb) and
|
||||
pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
|
||||
*
|
||||
* The flow from `src` to the returned node may be inter-procedural.
|
||||
*/
|
||||
cached
|
||||
DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) {
|
||||
@@ -371,9 +383,10 @@ module API {
|
||||
pred = MkRoot() and
|
||||
useRoot(lbl, ref)
|
||||
or
|
||||
exists(DataFlow::Node nd |
|
||||
pred = MkUse(nd) and
|
||||
useUse(nd, lbl, ref)
|
||||
exists(ExprCfgNode node, DataFlow::Node src |
|
||||
pred = MkUse(src) and
|
||||
trackUseNode(src).flowsTo(any(DataFlow::ExprNode n | n.getExprNode() = node)) and
|
||||
useStep(lbl, node, ref)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -417,6 +417,12 @@ module HTTP {
|
||||
/** Gets a node which returns the body of the response */
|
||||
DataFlow::Node getResponseBody() { result = super.getResponseBody() }
|
||||
|
||||
/**
|
||||
* Gets a node that contributes to the URL of the request.
|
||||
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
|
||||
*/
|
||||
DataFlow::Node getURL() { result = super.getURL() }
|
||||
|
||||
/** Gets a string that identifies the framework used for this request. */
|
||||
string getFramework() { result = super.getFramework() }
|
||||
|
||||
@@ -442,6 +448,12 @@ module HTTP {
|
||||
/** Gets a node which returns the body of the response */
|
||||
abstract DataFlow::Node getResponseBody();
|
||||
|
||||
/**
|
||||
* Gets a node that contributes to the URL of the request.
|
||||
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
|
||||
*/
|
||||
abstract DataFlow::Node getURL();
|
||||
|
||||
/** Gets a string that identifies the framework used for this request. */
|
||||
abstract string getFramework();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.regexp.RegExpTreeView as RETV
|
||||
private import codeql.ruby.security.performance.RegExpTreeView as RETV
|
||||
private import internal.AST
|
||||
private import internal.Scope
|
||||
private import internal.TreeSitter
|
||||
|
||||
@@ -167,7 +167,7 @@ private module Cached {
|
||||
TLTExpr(Ruby::Binary g) { g instanceof @ruby_binary_langle } or
|
||||
TLambda(Ruby::Lambda g) or
|
||||
TLeftAssignmentList(Ruby::LeftAssignmentList g) or
|
||||
TLocalVariableAccessReal(Ruby::Identifier g, AST::LocalVariable v) {
|
||||
TLocalVariableAccessReal(Ruby::Identifier g, TLocalVariableReal v) {
|
||||
LocalVariableAccess::range(g, v)
|
||||
} or
|
||||
TLocalVariableAccessSynth(AST::AstNode parent, int i, AST::LocalVariable v) {
|
||||
@@ -284,13 +284,55 @@ private module Cached {
|
||||
TWhileModifierExpr(Ruby::WhileModifier g) or
|
||||
TYieldCall(Ruby::Yield g)
|
||||
|
||||
class TAstNodeReal =
|
||||
TAddExprReal or TAliasStmt or TArgumentList or TAssignAddExpr or TAssignBitwiseAndExpr or
|
||||
TAssignBitwiseOrExpr or TAssignBitwiseXorExpr or TAssignDivExpr or TAssignExponentExpr or
|
||||
TAssignExprReal or TAssignLShiftExpr or TAssignLogicalAndExpr or TAssignLogicalOrExpr or
|
||||
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
|
||||
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
|
||||
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
|
||||
TLShiftExprReal or TLTExpr or TLambda or TLeftAssignmentList or TLocalVariableAccessReal or
|
||||
TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TModuleDeclaration or
|
||||
TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or TNilLiteral or
|
||||
TNoRegExpMatchExpr or TNotExpr or TOptionalParameter or TPair or TParenthesizedExpr or
|
||||
TRShiftExprReal or TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or
|
||||
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
|
||||
TSpaceshipExpr or TSplatExprReal or TSplatParameter or TStringArrayLiteral or
|
||||
TStringConcatenation or TStringEscapeSequenceComponent or TStringInterpolationComponent or
|
||||
TStringTextComponent or TSubExprReal or TSubshellLiteral or TSymbolArrayLiteral or
|
||||
TTernaryIfExpr or TThen or TTokenConstantAccess or TTokenMethodName or TTokenSuperCall or
|
||||
TToplevel or TTrueLiteral or TTuplePatternParameter or TUnaryMinusExpr or TUnaryPlusExpr or
|
||||
TUndefStmt or TUnlessExpr or TUnlessModifierExpr or TUntilExpr or TUntilModifierExpr or
|
||||
TWhenExpr or TWhileExpr or TWhileModifierExpr or TYieldCall;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Gets the underlying TreeSitter entity for a given AST node. This does not
|
||||
* include synthesized AST nodes, because they are not the primary AST node
|
||||
* for any given generated node.
|
||||
*/
|
||||
cached
|
||||
Ruby::AstNode toGenerated(AST::AstNode n) {
|
||||
Ruby::AstNode toGenerated(TAstNodeReal n) {
|
||||
n = TAddExprReal(result) or
|
||||
n = TAliasStmt(result) or
|
||||
n = TArgumentList(result) or
|
||||
@@ -495,7 +537,9 @@ private module Cached {
|
||||
predicate synthChild(AST::AstNode parent, int i, AST::AstNode child) {
|
||||
child = getSynthChild(parent, i)
|
||||
or
|
||||
any(Synthesis s).child(parent, i, RealChild(child))
|
||||
any(Synthesis s).child(parent, i, RealChildRef(child))
|
||||
or
|
||||
any(Synthesis s).child(parent, i, SynthChildRef(child))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -527,7 +571,7 @@ private module Cached {
|
||||
|
||||
import Cached
|
||||
|
||||
TAstNode fromGenerated(Ruby::AstNode n) { n = toGenerated(result) }
|
||||
TAstNodeReal fromGenerated(Ruby::AstNode n) { n = toGenerated(result) }
|
||||
|
||||
class TCall = TMethodCall or TYieldCall;
|
||||
|
||||
|
||||
@@ -106,6 +106,15 @@ private module Cached {
|
||||
exists(string qname | qname = resolveConstant(r) and result = TResolved(qname))
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private string constantDefinition1(ConstantReadAccess r) {
|
||||
exists(ConstantWriteAccess w | result = constantDefinition0(w) |
|
||||
r = w.getScopeExpr()
|
||||
or
|
||||
r = w.(ClassDeclaration).getSuperclassExpr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve constant access (class, module or otherwise) to a qualified module name.
|
||||
* `resolveScopeExpr/1` picks the best (lowest priority number) result of
|
||||
@@ -121,11 +130,7 @@ private module Cached {
|
||||
isDefinedConstant(qn) and
|
||||
qn = resolveScopeExpr(r, p) and
|
||||
// prevent classes/modules that contain/extend themselves
|
||||
not exists(ConstantWriteAccess w | qn = constantDefinition0(w) |
|
||||
r = w.getScopeExpr()
|
||||
or
|
||||
r = w.(ClassDeclaration).getSuperclassExpr()
|
||||
)
|
||||
not qn = constantDefinition1(r)
|
||||
|
|
||||
qn order by p
|
||||
)
|
||||
|
||||
@@ -46,7 +46,25 @@ newtype SynthKind =
|
||||
*/
|
||||
newtype Child =
|
||||
SynthChild(SynthKind k) or
|
||||
RealChild(AstNode n)
|
||||
RealChildRef(TAstNodeReal n) or
|
||||
SynthChildRef(TAstNodeSynth n)
|
||||
|
||||
/**
|
||||
* The purpose of this inlined predicate is to split up child references into
|
||||
* those that are from real AST nodes (for which there will be no recursion
|
||||
* through `RealChildRef`), and those that are synthesized recursively
|
||||
* (for which there will be recursion through `SynthChildRef`).
|
||||
*
|
||||
* This performs much better than having a combined `ChildRef` that includes
|
||||
* both real and synthesized AST nodes, since the recursion happening in
|
||||
* `Synthesis::child/3` is non-linear.
|
||||
*/
|
||||
pragma[inline]
|
||||
private Child childRef(TAstNode n) {
|
||||
result = RealChildRef(n)
|
||||
or
|
||||
result = SynthChildRef(n)
|
||||
}
|
||||
|
||||
private newtype TSynthesis = MkSynthesis()
|
||||
|
||||
@@ -125,7 +143,7 @@ private predicate assign(
|
||||
child = SynthChild(LocalVariableAccessSynthKind(v))
|
||||
or
|
||||
i = 1 and
|
||||
child = RealChild(value)
|
||||
child = childRef(value)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -218,10 +236,10 @@ private module SetterDesugar {
|
||||
exists(AstNode call | call = TMethodCallSynth(seq, 0, _, _, _) |
|
||||
parent = call and
|
||||
i = 0 and
|
||||
child = RealChild(sae.getReceiver())
|
||||
child = childRef(sae.getReceiver())
|
||||
or
|
||||
parent = call and
|
||||
child = RealChild(sae.getArgument(i - 1))
|
||||
child = childRef(sae.getArgument(i - 1))
|
||||
or
|
||||
exists(int valueIndex | valueIndex = sae.getNumberOfArguments() + 1 |
|
||||
parent = call and
|
||||
@@ -234,7 +252,7 @@ private module SetterDesugar {
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(sae, 0)))
|
||||
or
|
||||
i = 1 and
|
||||
child = RealChild(sae.getRightOperand())
|
||||
child = childRef(sae.getRightOperand())
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -357,7 +375,7 @@ private module AssignOperationDesugar {
|
||||
exists(AstNode assign | assign = TAssignExprSynth(vao, -1) |
|
||||
parent = assign and
|
||||
i = 0 and
|
||||
child = RealChild(vao.getLeftOperand())
|
||||
child = childRef(vao.getLeftOperand())
|
||||
or
|
||||
parent = assign and
|
||||
i = 1 and
|
||||
@@ -369,7 +387,7 @@ private module AssignOperationDesugar {
|
||||
child = SynthChild(vao.getVariableAccessKind())
|
||||
or
|
||||
i = 1 and
|
||||
child = RealChild(vao.getRightOperand())
|
||||
child = childRef(vao.getRightOperand())
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -477,7 +495,7 @@ private module AssignOperationDesugar {
|
||||
or
|
||||
parent = op and
|
||||
i = 1 and
|
||||
child = RealChild(sao.getRightOperand())
|
||||
child = childRef(sao.getRightOperand())
|
||||
)
|
||||
)
|
||||
or
|
||||
@@ -648,7 +666,7 @@ private module CompoundAssignDesugar {
|
||||
or
|
||||
parent = TSplatExprSynth(assign, 1) and
|
||||
i = 0 and
|
||||
child = RealChild(tae.getRightOperand())
|
||||
child = childRef(tae.getRightOperand())
|
||||
)
|
||||
or
|
||||
exists(Pattern p, int j, int restIndex |
|
||||
@@ -662,7 +680,7 @@ private module CompoundAssignDesugar {
|
||||
exists(AstNode assign | assign = TAssignExprSynth(seq, j + 1) |
|
||||
parent = assign and
|
||||
i = 0 and
|
||||
child = RealChild(p)
|
||||
child = childRef(p)
|
||||
or
|
||||
parent = assign and
|
||||
i = 1 and
|
||||
@@ -767,7 +785,7 @@ private module ArrayLiteralDesugar {
|
||||
child = SynthChild(ConstantReadAccessKind("::Array"))
|
||||
or
|
||||
parent = mc and
|
||||
child = RealChild(al.getElement(i - 1))
|
||||
child = childRef(al.getElement(i - 1))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* Automatically generated from the tree-sitter grammar; do not edit
|
||||
*/
|
||||
|
||||
private import codeql.files.FileSystem
|
||||
private import codeql.Locations
|
||||
import codeql.Locations as L
|
||||
|
||||
module Ruby {
|
||||
/** The base class for all AST nodes */
|
||||
@@ -13,7 +12,7 @@ module Ruby {
|
||||
string toString() { result = this.getAPrimaryQlClass() }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
Location getLocation() { none() }
|
||||
L::Location getLocation() { none() }
|
||||
|
||||
/** Gets the parent of this element. */
|
||||
AstNode getParent() { ruby_ast_node_parent(this, result, _) }
|
||||
@@ -37,7 +36,7 @@ module Ruby {
|
||||
string getValue() { ruby_tokeninfo(this, _, result, _) }
|
||||
|
||||
/** Gets the location of this token. */
|
||||
override Location getLocation() { ruby_tokeninfo(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_tokeninfo(this, _, _, result) }
|
||||
|
||||
/** Gets a string representation of this element. */
|
||||
override string toString() { result = this.getValue() }
|
||||
@@ -70,7 +69,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Alias" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_alias_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_alias_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `alias`. */
|
||||
UnderscoreMethodName getAlias() { ruby_alias_def(this, result, _, _) }
|
||||
@@ -90,7 +89,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "ArgumentList" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_argument_list_def(this, result) }
|
||||
override L::Location getLocation() { ruby_argument_list_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_argument_list_child(this, i, result) }
|
||||
@@ -105,7 +104,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Array" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_array_def(this, result) }
|
||||
override L::Location getLocation() { ruby_array_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_array_child(this, i, result) }
|
||||
@@ -120,7 +119,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Assignment" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_assignment_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_assignment_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `left`. */
|
||||
AstNode getLeft() { ruby_assignment_def(this, result, _, _) }
|
||||
@@ -140,7 +139,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "BareString" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_bare_string_def(this, result) }
|
||||
override L::Location getLocation() { ruby_bare_string_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_bare_string_child(this, i, result) }
|
||||
@@ -155,7 +154,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "BareSymbol" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_bare_symbol_def(this, result) }
|
||||
override L::Location getLocation() { ruby_bare_symbol_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_bare_symbol_child(this, i, result) }
|
||||
@@ -170,7 +169,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Begin" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_begin_def(this, result) }
|
||||
override L::Location getLocation() { ruby_begin_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_begin_child(this, i, result) }
|
||||
@@ -185,7 +184,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "BeginBlock" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_begin_block_def(this, result) }
|
||||
override L::Location getLocation() { ruby_begin_block_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_begin_block_child(this, i, result) }
|
||||
@@ -200,7 +199,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Binary" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_binary_def(this, _, _, _, result) }
|
||||
override L::Location getLocation() { ruby_binary_def(this, _, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `left`. */
|
||||
AstNode getLeft() { ruby_binary_def(this, result, _, _, _) }
|
||||
@@ -275,7 +274,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Block" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_block_def(this, result) }
|
||||
override L::Location getLocation() { ruby_block_def(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `parameters`. */
|
||||
BlockParameters getParameters() { ruby_block_parameters(this, result) }
|
||||
@@ -295,7 +294,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "BlockArgument" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_block_argument_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_block_argument_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
UnderscoreArg getChild() { ruby_block_argument_def(this, result, _) }
|
||||
@@ -310,7 +309,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "BlockParameter" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_block_parameter_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_block_parameter_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
Identifier getName() { ruby_block_parameter_def(this, result, _) }
|
||||
@@ -325,7 +324,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "BlockParameters" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_block_parameters_def(this, result) }
|
||||
override L::Location getLocation() { ruby_block_parameters_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_block_parameters_child(this, i, result) }
|
||||
@@ -340,7 +339,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Break" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_break_def(this, result) }
|
||||
override L::Location getLocation() { ruby_break_def(this, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
ArgumentList getChild() { ruby_break_child(this, result) }
|
||||
@@ -355,7 +354,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Call" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_call_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_call_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `arguments`. */
|
||||
ArgumentList getArguments() { ruby_call_arguments(this, result) }
|
||||
@@ -384,7 +383,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Case" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_case_def(this, result) }
|
||||
override L::Location getLocation() { ruby_case_def(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `value`. */
|
||||
UnderscoreStatement getValue() { ruby_case_value(this, result) }
|
||||
@@ -404,7 +403,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "ChainedString" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_chained_string_def(this, result) }
|
||||
override L::Location getLocation() { ruby_chained_string_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
String getChild(int i) { ruby_chained_string_child(this, i, result) }
|
||||
@@ -425,7 +424,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Class" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_class_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_class_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
AstNode getName() { ruby_class_def(this, result, _) }
|
||||
@@ -468,7 +467,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Conditional" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_conditional_def(this, _, _, _, result) }
|
||||
override L::Location getLocation() { ruby_conditional_def(this, _, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `alternative`. */
|
||||
UnderscoreArg getAlternative() { ruby_conditional_def(this, result, _, _, _) }
|
||||
@@ -499,7 +498,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "DelimitedSymbol" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_delimited_symbol_def(this, result) }
|
||||
override L::Location getLocation() { ruby_delimited_symbol_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_delimited_symbol_child(this, i, result) }
|
||||
@@ -514,7 +513,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "DestructuredLeftAssignment" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_destructured_left_assignment_def(this, result) }
|
||||
override L::Location getLocation() { ruby_destructured_left_assignment_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_destructured_left_assignment_child(this, i, result) }
|
||||
@@ -529,7 +528,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "DestructuredParameter" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_destructured_parameter_def(this, result) }
|
||||
override L::Location getLocation() { ruby_destructured_parameter_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_destructured_parameter_child(this, i, result) }
|
||||
@@ -544,7 +543,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Do" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_do_def(this, result) }
|
||||
override L::Location getLocation() { ruby_do_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_do_child(this, i, result) }
|
||||
@@ -559,7 +558,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "DoBlock" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_do_block_def(this, result) }
|
||||
override L::Location getLocation() { ruby_do_block_def(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `parameters`. */
|
||||
BlockParameters getParameters() { ruby_do_block_parameters(this, result) }
|
||||
@@ -579,7 +578,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "ElementReference" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_element_reference_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_element_reference_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `object`. */
|
||||
UnderscorePrimary getObject() { ruby_element_reference_def(this, result, _) }
|
||||
@@ -599,7 +598,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Else" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_else_def(this, result) }
|
||||
override L::Location getLocation() { ruby_else_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_else_child(this, i, result) }
|
||||
@@ -614,7 +613,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Elsif" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_elsif_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_elsif_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `alternative`. */
|
||||
AstNode getAlternative() { ruby_elsif_alternative(this, result) }
|
||||
@@ -645,7 +644,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "EndBlock" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_end_block_def(this, result) }
|
||||
override L::Location getLocation() { ruby_end_block_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_end_block_child(this, i, result) }
|
||||
@@ -660,7 +659,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Ensure" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_ensure_def(this, result) }
|
||||
override L::Location getLocation() { ruby_ensure_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_ensure_child(this, i, result) }
|
||||
@@ -681,7 +680,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "ExceptionVariable" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_exception_variable_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_exception_variable_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
UnderscoreLhs getChild() { ruby_exception_variable_def(this, result, _) }
|
||||
@@ -696,7 +695,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Exceptions" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_exceptions_def(this, result) }
|
||||
override L::Location getLocation() { ruby_exceptions_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_exceptions_child(this, i, result) }
|
||||
@@ -723,7 +722,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "For" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_for_def(this, _, _, _, result) }
|
||||
override L::Location getLocation() { ruby_for_def(this, _, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
Do getBody() { ruby_for_def(this, result, _, _, _) }
|
||||
@@ -766,7 +765,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Hash" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_hash_def(this, result) }
|
||||
override L::Location getLocation() { ruby_hash_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_hash_child(this, i, result) }
|
||||
@@ -787,7 +786,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "HashSplatArgument" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_hash_splat_argument_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_hash_splat_argument_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
UnderscoreArg getChild() { ruby_hash_splat_argument_def(this, result, _) }
|
||||
@@ -802,7 +801,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "HashSplatParameter" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_hash_splat_parameter_def(this, result) }
|
||||
override L::Location getLocation() { ruby_hash_splat_parameter_def(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
Identifier getName() { ruby_hash_splat_parameter_name(this, result) }
|
||||
@@ -823,7 +822,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "HeredocBody" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_heredoc_body_def(this, result) }
|
||||
override L::Location getLocation() { ruby_heredoc_body_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_heredoc_body_child(this, i, result) }
|
||||
@@ -856,7 +855,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "If" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_if_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_if_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `alternative`. */
|
||||
AstNode getAlternative() { ruby_if_alternative(this, result) }
|
||||
@@ -881,7 +880,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "IfModifier" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_if_modifier_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_if_modifier_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
UnderscoreStatement getBody() { ruby_if_modifier_def(this, result, _, _) }
|
||||
@@ -901,7 +900,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "In" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_in_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_in_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
UnderscoreArg getChild() { ruby_in_def(this, result, _) }
|
||||
@@ -928,7 +927,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Interpolation" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_interpolation_def(this, result) }
|
||||
override L::Location getLocation() { ruby_interpolation_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_interpolation_child(this, i, result) }
|
||||
@@ -943,7 +942,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "KeywordParameter" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_keyword_parameter_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_keyword_parameter_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
Identifier getName() { ruby_keyword_parameter_def(this, result, _) }
|
||||
@@ -963,7 +962,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Lambda" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_lambda_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_lambda_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
AstNode getBody() { ruby_lambda_def(this, result, _) }
|
||||
@@ -983,7 +982,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "LambdaParameters" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_lambda_parameters_def(this, result) }
|
||||
override L::Location getLocation() { ruby_lambda_parameters_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_lambda_parameters_child(this, i, result) }
|
||||
@@ -998,7 +997,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "LeftAssignmentList" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_left_assignment_list_def(this, result) }
|
||||
override L::Location getLocation() { ruby_left_assignment_list_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_left_assignment_list_child(this, i, result) }
|
||||
@@ -1013,7 +1012,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Method" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_method_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_method_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
UnderscoreMethodName getName() { ruby_method_def(this, result, _) }
|
||||
@@ -1038,7 +1037,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "MethodParameters" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_method_parameters_def(this, result) }
|
||||
override L::Location getLocation() { ruby_method_parameters_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_method_parameters_child(this, i, result) }
|
||||
@@ -1053,7 +1052,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Module" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_module_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_module_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
AstNode getName() { ruby_module_def(this, result, _) }
|
||||
@@ -1073,7 +1072,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Next" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_next_def(this, result) }
|
||||
override L::Location getLocation() { ruby_next_def(this, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
ArgumentList getChild() { ruby_next_child(this, result) }
|
||||
@@ -1100,7 +1099,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "OperatorAssignment" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_operator_assignment_def(this, _, _, _, result) }
|
||||
override L::Location getLocation() { ruby_operator_assignment_def(this, _, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `left`. */
|
||||
UnderscoreLhs getLeft() { ruby_operator_assignment_def(this, result, _, _, _) }
|
||||
@@ -1152,7 +1151,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "OptionalParameter" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_optional_parameter_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_optional_parameter_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
Identifier getName() { ruby_optional_parameter_def(this, result, _, _) }
|
||||
@@ -1173,7 +1172,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Pair" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_pair_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_pair_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `key`. */
|
||||
AstNode getKey() { ruby_pair_def(this, result, _, _) }
|
||||
@@ -1193,7 +1192,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "ParenthesizedStatements" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_parenthesized_statements_def(this, result) }
|
||||
override L::Location getLocation() { ruby_parenthesized_statements_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_parenthesized_statements_child(this, i, result) }
|
||||
@@ -1208,7 +1207,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Pattern" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_pattern_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_pattern_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
AstNode getChild() { ruby_pattern_def(this, result, _) }
|
||||
@@ -1223,7 +1222,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Program" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_program_def(this, result) }
|
||||
override L::Location getLocation() { ruby_program_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_program_child(this, i, result) }
|
||||
@@ -1238,7 +1237,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Range" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_range_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_range_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `begin`. */
|
||||
UnderscoreArg getBegin() { ruby_range_begin(this, result) }
|
||||
@@ -1267,7 +1266,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Rational" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_rational_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_rational_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
AstNode getChild() { ruby_rational_def(this, result, _) }
|
||||
@@ -1282,7 +1281,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Redo" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_redo_def(this, result) }
|
||||
override L::Location getLocation() { ruby_redo_def(this, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
ArgumentList getChild() { ruby_redo_child(this, result) }
|
||||
@@ -1297,7 +1296,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Regex" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_regex_def(this, result) }
|
||||
override L::Location getLocation() { ruby_regex_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_regex_child(this, i, result) }
|
||||
@@ -1312,7 +1311,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Rescue" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_rescue_def(this, result) }
|
||||
override L::Location getLocation() { ruby_rescue_def(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
Then getBody() { ruby_rescue_body(this, result) }
|
||||
@@ -1337,7 +1336,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "RescueModifier" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_rescue_modifier_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_rescue_modifier_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
UnderscoreStatement getBody() { ruby_rescue_modifier_def(this, result, _, _) }
|
||||
@@ -1357,7 +1356,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "RestAssignment" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_rest_assignment_def(this, result) }
|
||||
override L::Location getLocation() { ruby_rest_assignment_def(this, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
UnderscoreLhs getChild() { ruby_rest_assignment_child(this, result) }
|
||||
@@ -1372,7 +1371,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Retry" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_retry_def(this, result) }
|
||||
override L::Location getLocation() { ruby_retry_def(this, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
ArgumentList getChild() { ruby_retry_child(this, result) }
|
||||
@@ -1387,7 +1386,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Return" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_return_def(this, result) }
|
||||
override L::Location getLocation() { ruby_return_def(this, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
ArgumentList getChild() { ruby_return_child(this, result) }
|
||||
@@ -1402,7 +1401,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "RightAssignmentList" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_right_assignment_list_def(this, result) }
|
||||
override L::Location getLocation() { ruby_right_assignment_list_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_right_assignment_list_child(this, i, result) }
|
||||
@@ -1417,7 +1416,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "ScopeResolution" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_scope_resolution_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_scope_resolution_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
AstNode getName() { ruby_scope_resolution_def(this, result, _) }
|
||||
@@ -1443,7 +1442,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Setter" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_setter_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_setter_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
Identifier getName() { ruby_setter_def(this, result, _) }
|
||||
@@ -1464,7 +1463,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "SingletonClass" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_singleton_class_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_singleton_class_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `value`. */
|
||||
UnderscoreArg getValue() { ruby_singleton_class_def(this, result, _) }
|
||||
@@ -1484,7 +1483,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "SingletonMethod" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_singleton_method_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_singleton_method_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
UnderscoreMethodName getName() { ruby_singleton_method_def(this, result, _, _) }
|
||||
@@ -1513,7 +1512,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "SplatArgument" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_splat_argument_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_splat_argument_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
UnderscoreArg getChild() { ruby_splat_argument_def(this, result, _) }
|
||||
@@ -1528,7 +1527,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "SplatParameter" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_splat_parameter_def(this, result) }
|
||||
override L::Location getLocation() { ruby_splat_parameter_def(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `name`. */
|
||||
Identifier getName() { ruby_splat_parameter_name(this, result) }
|
||||
@@ -1543,7 +1542,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "String" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_string_def(this, result) }
|
||||
override L::Location getLocation() { ruby_string_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_string_child(this, i, result) }
|
||||
@@ -1558,7 +1557,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "StringArray" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_string_array_def(this, result) }
|
||||
override L::Location getLocation() { ruby_string_array_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
BareString getChild(int i) { ruby_string_array_child(this, i, result) }
|
||||
@@ -1579,7 +1578,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Subshell" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_subshell_def(this, result) }
|
||||
override L::Location getLocation() { ruby_subshell_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_subshell_child(this, i, result) }
|
||||
@@ -1600,7 +1599,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Superclass" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_superclass_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_superclass_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
AstNode getChild() { ruby_superclass_def(this, result, _) }
|
||||
@@ -1615,7 +1614,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "SymbolArray" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_symbol_array_def(this, result) }
|
||||
override L::Location getLocation() { ruby_symbol_array_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
BareSymbol getChild(int i) { ruby_symbol_array_child(this, i, result) }
|
||||
@@ -1630,7 +1629,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Then" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_then_def(this, result) }
|
||||
override L::Location getLocation() { ruby_then_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { ruby_then_child(this, i, result) }
|
||||
@@ -1651,7 +1650,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Unary" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_unary_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_unary_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `operand`. */
|
||||
AstNode getOperand() { ruby_unary_def(this, result, _, _) }
|
||||
@@ -1683,7 +1682,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Undef" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_undef_def(this, result) }
|
||||
override L::Location getLocation() { ruby_undef_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
UnderscoreMethodName getChild(int i) { ruby_undef_child(this, i, result) }
|
||||
@@ -1704,7 +1703,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Unless" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_unless_def(this, _, result) }
|
||||
override L::Location getLocation() { ruby_unless_def(this, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `alternative`. */
|
||||
AstNode getAlternative() { ruby_unless_alternative(this, result) }
|
||||
@@ -1729,7 +1728,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "UnlessModifier" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_unless_modifier_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_unless_modifier_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
UnderscoreStatement getBody() { ruby_unless_modifier_def(this, result, _, _) }
|
||||
@@ -1749,7 +1748,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Until" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_until_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_until_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
Do getBody() { ruby_until_def(this, result, _, _) }
|
||||
@@ -1769,7 +1768,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "UntilModifier" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_until_modifier_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_until_modifier_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
UnderscoreStatement getBody() { ruby_until_modifier_def(this, result, _, _) }
|
||||
@@ -1789,7 +1788,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "When" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_when_def(this, result) }
|
||||
override L::Location getLocation() { ruby_when_def(this, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
Then getBody() { ruby_when_body(this, result) }
|
||||
@@ -1809,7 +1808,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "While" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_while_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_while_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
Do getBody() { ruby_while_def(this, result, _, _) }
|
||||
@@ -1829,7 +1828,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "WhileModifier" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_while_modifier_def(this, _, _, result) }
|
||||
override L::Location getLocation() { ruby_while_modifier_def(this, _, _, result) }
|
||||
|
||||
/** Gets the node corresponding to the field `body`. */
|
||||
UnderscoreStatement getBody() { ruby_while_modifier_def(this, result, _, _) }
|
||||
@@ -1849,7 +1848,7 @@ module Ruby {
|
||||
override string getAPrimaryQlClass() { result = "Yield" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { ruby_yield_def(this, result) }
|
||||
override L::Location getLocation() { ruby_yield_def(this, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
ArgumentList getChild() { ruby_yield_child(this, result) }
|
||||
@@ -1866,7 +1865,7 @@ module Erb {
|
||||
string toString() { result = this.getAPrimaryQlClass() }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
Location getLocation() { none() }
|
||||
L::Location getLocation() { none() }
|
||||
|
||||
/** Gets the parent of this element. */
|
||||
AstNode getParent() { erb_ast_node_parent(this, result, _) }
|
||||
@@ -1890,7 +1889,7 @@ module Erb {
|
||||
string getValue() { erb_tokeninfo(this, _, result, _) }
|
||||
|
||||
/** Gets the location of this token. */
|
||||
override Location getLocation() { erb_tokeninfo(this, _, _, result) }
|
||||
override L::Location getLocation() { erb_tokeninfo(this, _, _, result) }
|
||||
|
||||
/** Gets a string representation of this element. */
|
||||
override string toString() { result = this.getValue() }
|
||||
@@ -1923,7 +1922,7 @@ module Erb {
|
||||
override string getAPrimaryQlClass() { result = "CommentDirective" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { erb_comment_directive_def(this, _, result) }
|
||||
override L::Location getLocation() { erb_comment_directive_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
Comment getChild() { erb_comment_directive_def(this, result, _) }
|
||||
@@ -1944,7 +1943,7 @@ module Erb {
|
||||
override string getAPrimaryQlClass() { result = "Directive" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { erb_directive_def(this, _, result) }
|
||||
override L::Location getLocation() { erb_directive_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
Code getChild() { erb_directive_def(this, result, _) }
|
||||
@@ -1959,7 +1958,7 @@ module Erb {
|
||||
override string getAPrimaryQlClass() { result = "GraphqlDirective" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { erb_graphql_directive_def(this, _, result) }
|
||||
override L::Location getLocation() { erb_graphql_directive_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
Code getChild() { erb_graphql_directive_def(this, result, _) }
|
||||
@@ -1974,7 +1973,7 @@ module Erb {
|
||||
override string getAPrimaryQlClass() { result = "OutputDirective" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { erb_output_directive_def(this, _, result) }
|
||||
override L::Location getLocation() { erb_output_directive_def(this, _, result) }
|
||||
|
||||
/** Gets the child of this node. */
|
||||
Code getChild() { erb_output_directive_def(this, result, _) }
|
||||
@@ -1989,7 +1988,7 @@ module Erb {
|
||||
override string getAPrimaryQlClass() { result = "Template" }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
override Location getLocation() { erb_template_def(this, result) }
|
||||
override L::Location getLocation() { erb_template_def(this, result) }
|
||||
|
||||
/** Gets the `i`th child of this node. */
|
||||
AstNode getChild(int i) { erb_template_child(this, i, result) }
|
||||
|
||||
@@ -495,7 +495,7 @@ abstract class VariableAccessImpl extends Expr, TVariableAccess {
|
||||
}
|
||||
|
||||
module LocalVariableAccess {
|
||||
predicate range(Ruby::Identifier id, LocalVariable v) {
|
||||
predicate range(Ruby::Identifier id, TLocalVariableReal v) {
|
||||
access(id, v) and
|
||||
(
|
||||
explicitWriteAccess(id, _)
|
||||
|
||||
@@ -320,7 +320,11 @@ module ExprNodes {
|
||||
|
||||
/** Gets the the keyword argument whose key is `keyword` of this call. */
|
||||
final ExprCfgNode getKeywordArgument(string keyword) {
|
||||
e.hasCfgChild(e.getKeywordArgument(keyword), this, result)
|
||||
exists(PairCfgNode n |
|
||||
e.hasCfgChild(e.getAnArgument(), this, n) and
|
||||
n.getKey().getExpr().(SymbolLiteral).getValueText() = keyword and
|
||||
result = n.getValue()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the number of arguments of this call. */
|
||||
@@ -426,6 +430,27 @@ module ExprNodes {
|
||||
ParenthesizedExprCfgNode() { this.getExpr() instanceof ParenthesizedExpr }
|
||||
}
|
||||
|
||||
private class PairChildMapping extends ExprChildMapping, Pair {
|
||||
override predicate relevantChild(Expr e) { e = this.getKey() or e = this.getValue() }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `Pair` AST expression. */
|
||||
class PairCfgNode extends ExprCfgNode {
|
||||
override PairChildMapping e;
|
||||
|
||||
final override Pair getExpr() { result = ExprCfgNode.super.getExpr() }
|
||||
|
||||
/**
|
||||
* Gets the key expression of this pair.
|
||||
*/
|
||||
final ExprCfgNode getKey() { e.hasCfgChild(e.getKey(), this, result) }
|
||||
|
||||
/**
|
||||
* Gets the value expression of this pair.
|
||||
*/
|
||||
final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) }
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `VariableReadAccess` AST expression. */
|
||||
class VariableReadAccessCfgNode extends ExprCfgNode {
|
||||
override VariableReadAccess e;
|
||||
|
||||
@@ -608,6 +608,8 @@ module Trees {
|
||||
}
|
||||
}
|
||||
|
||||
private class ForwardParameterTree extends LeafTree, ForwardParameter { }
|
||||
|
||||
private class ForInTree extends LeafTree, ForIn { }
|
||||
|
||||
/**
|
||||
|
||||
@@ -924,7 +924,8 @@ module Consistency {
|
||||
succSplits(pred, predSplits, succ, succSplits, c) and
|
||||
split.hasEntry(pred, succ, c) and
|
||||
not split.getKind() = predSplits.getASplit().getKind() and
|
||||
not split = succSplits.getASplit()
|
||||
not split = succSplits.getASplit() and
|
||||
split.getKind().isEnabled(succ)
|
||||
}
|
||||
|
||||
query predicate breakInvariant5(
|
||||
@@ -942,4 +943,9 @@ module Consistency {
|
||||
strictcount(getASuccessor(node, t)) > 1 and
|
||||
successor = getASuccessor(node, t)
|
||||
}
|
||||
|
||||
query predicate deadEnd(Node node) {
|
||||
not node instanceof TExitNode and
|
||||
not exists(getASuccessor(node, _))
|
||||
}
|
||||
}
|
||||
|
||||
16
ruby/ql/lib/codeql/ruby/dataflow/Sanitizers.qll
Normal file
16
ruby/ql/lib/codeql/ruby/dataflow/Sanitizers.qll
Normal file
@@ -0,0 +1,16 @@
|
||||
/** Provides commonly used dataflow sanitizers */
|
||||
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.DataFlow
|
||||
|
||||
/**
|
||||
* A sanitizer for flow into a string interpolation component,
|
||||
* provided that component does not form a prefix of the string.
|
||||
*
|
||||
* This is useful for URLs and paths, where the fixed prefix prevents the user from controlling the target.
|
||||
*/
|
||||
class PrefixedStringInterpolation extends DataFlow::Node {
|
||||
PrefixedStringInterpolation() {
|
||||
exists(StringlikeLiteral str, int n | str.getComponent(n) = this.asExpr().getExpr() and n > 0)
|
||||
}
|
||||
}
|
||||
@@ -450,7 +450,7 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
|
||||
*/
|
||||
predicate exprNodeReturnedFrom(DataFlow::ExprNode e, Callable c) {
|
||||
exists(ReturningNode r |
|
||||
r.getEnclosingCallable().asCallable() = c and
|
||||
nodeGetEnclosingCallable(r).asCallable() = c and
|
||||
(
|
||||
r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or
|
||||
r.(ExprReturnNode) = e
|
||||
|
||||
@@ -3740,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3780,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3790,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -3740,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3780,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3790,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -7,12 +7,16 @@ private import SsaImpl as SsaImpl
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
|
||||
/** Gets the callable in which this node occurs. */
|
||||
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
|
||||
DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() }
|
||||
|
||||
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
|
||||
predicate isParameterNode(ParameterNodeImpl p, DataFlowCallable c, int pos) {
|
||||
p.isParameterOf(c, pos)
|
||||
}
|
||||
|
||||
abstract class NodeImpl extends Node {
|
||||
DataFlowCallable getEnclosingCallable() { result = TCfgScope(this.getCfgScope()) }
|
||||
|
||||
/** Do not call: use `getEnclosingCallable()` instead. */
|
||||
abstract CfgScope getCfgScope();
|
||||
|
||||
@@ -312,7 +316,7 @@ private module ParameterNodes {
|
||||
abstract class ParameterNodeImpl extends ParameterNode, NodeImpl {
|
||||
abstract predicate isSourceParameterOf(Callable c, int i);
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, int i) {
|
||||
predicate isParameterOf(DataFlowCallable c, int i) {
|
||||
this.isSourceParameterOf(c.asCallable(), i)
|
||||
}
|
||||
}
|
||||
@@ -802,7 +806,7 @@ predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
|
||||
)
|
||||
or
|
||||
receiver = call.(SummaryCall).getReceiver() and
|
||||
if receiver.(ParameterNode).isParameterOf(_, -2)
|
||||
if receiver.(ParameterNodeImpl).isParameterOf(_, -2)
|
||||
then kind = TYieldCallKind()
|
||||
else kind = TLambdaCallKind()
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ class Node extends TNode {
|
||||
// TODO: cache
|
||||
final Location getLocation() { result = this.(NodeImpl).getLocationImpl() }
|
||||
|
||||
DataFlowCallable getEnclosingCallable() { result = TCfgScope(this.(NodeImpl).getCfgScope()) }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
@@ -60,6 +58,9 @@ class CallNode extends LocalSourceNode {
|
||||
|
||||
/** Gets the data-flow node corresponding to the named argument of the call corresponding to this data-flow node */
|
||||
Node getKeywordArgument(string name) { result.asExpr() = node.getKeywordArgument(name) }
|
||||
|
||||
/** Gets the name of the the method called by the method call (if any) corresponding to this data-flow node */
|
||||
string getMethodName() { result = node.getExpr().(MethodCall).getMethodName() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,12 +86,6 @@ class ExprNode extends Node, TExprNode {
|
||||
class ParameterNode extends Node, TParameterNode {
|
||||
/** Gets the parameter corresponding to this node, if any. */
|
||||
Parameter getParameter() { none() }
|
||||
|
||||
/**
|
||||
* Holds if this node is the parameter of callable `c` at the specified
|
||||
* (zero-based) position.
|
||||
*/
|
||||
predicate isParameterOf(DataFlowCallable c, int i) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,6 +105,14 @@ class LocalSourceNode extends Node {
|
||||
*/
|
||||
pragma[inline]
|
||||
LocalSourceNode track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a node that may flow into this one using one heap and/or interprocedural step.
|
||||
*
|
||||
* See `TypeBackTracker` for more details about how to use this.
|
||||
*/
|
||||
pragma[inline]
|
||||
LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
|
||||
}
|
||||
|
||||
predicate hasLocalSource(Node sink, Node source) {
|
||||
|
||||
@@ -85,6 +85,9 @@ module Public {
|
||||
/** Holds if this stack contains summary component `c`. */
|
||||
predicate contains(SummaryComponent c) { c = this.drop(_).head() }
|
||||
|
||||
/** Gets the bottom element of this stack. */
|
||||
SummaryComponent bottom() { result = this.drop(this.length() - 1).head() }
|
||||
|
||||
/** Gets a textual representation of this stack. */
|
||||
string toString() {
|
||||
exists(SummaryComponent head, SummaryComponentStack tail |
|
||||
@@ -197,6 +200,8 @@ module Private {
|
||||
or
|
||||
tail.(RequiredSummaryComponentStack).required(TParameterSummaryComponent(_)) and
|
||||
head = thisParam()
|
||||
or
|
||||
derivedFluentFlowPush(_, _, _, head, tail, _)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
@@ -210,7 +215,7 @@ module Private {
|
||||
c.propagatesFlow(output, input, preservesValue) and
|
||||
preservesValue = true and
|
||||
isCallbackParameter(input) and
|
||||
isContentOfArgument(output)
|
||||
isContentOfArgument(output, _)
|
||||
or
|
||||
// flow from the receiver of a callback into the instance-parameter
|
||||
exists(SummaryComponentStack s, SummaryComponentStack callbackRef |
|
||||
@@ -222,16 +227,81 @@ module Private {
|
||||
output = TConsSummaryComponentStack(thisParam(), input) and
|
||||
preservesValue = true
|
||||
)
|
||||
or
|
||||
exists(SummaryComponentStack arg, SummaryComponentStack return |
|
||||
derivedFluentFlow(c, input, arg, return, preservesValue)
|
||||
|
|
||||
arg.length() = 1 and
|
||||
output = return
|
||||
or
|
||||
exists(SummaryComponent head, SummaryComponentStack tail |
|
||||
derivedFluentFlowPush(c, input, arg, head, tail, 0) and
|
||||
output = SummaryComponentStack::push(head, tail)
|
||||
)
|
||||
)
|
||||
or
|
||||
// Chain together summaries where values get passed into callbacks along the way
|
||||
exists(SummaryComponentStack mid, boolean preservesValue1, boolean preservesValue2 |
|
||||
c.propagatesFlow(input, mid, preservesValue1) and
|
||||
c.propagatesFlow(mid, output, preservesValue2) and
|
||||
mid.drop(mid.length() - 2) =
|
||||
SummaryComponentStack::push(TParameterSummaryComponent(_),
|
||||
SummaryComponentStack::singleton(TArgumentSummaryComponent(_))) and
|
||||
preservesValue = preservesValue1.booleanAnd(preservesValue2)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `c` has a flow summary from `input` to `arg`, where `arg`
|
||||
* writes to (contents of) the `i`th argument, and `c` has a
|
||||
* value-preserving flow summary from the `i`th argument to a return value
|
||||
* (`return`).
|
||||
*
|
||||
* In such a case, we derive flow from `input` to (contents of) the return
|
||||
* value.
|
||||
*
|
||||
* As an example, this simplifies modeling of fluent methods:
|
||||
* for `StringBuilder.append(x)` with a specified value flow from qualifier to
|
||||
* return value and taint flow from argument 0 to the qualifier, then this
|
||||
* allows us to infer taint flow from argument 0 to the return value.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate derivedFluentFlow(
|
||||
SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg,
|
||||
SummaryComponentStack return, boolean preservesValue
|
||||
) {
|
||||
exists(int i |
|
||||
summary(c, input, arg, preservesValue) and
|
||||
isContentOfArgument(arg, i) and
|
||||
summary(c, SummaryComponentStack::singleton(TArgumentSummaryComponent(i)), return, true) and
|
||||
return.bottom() = TReturnSummaryComponent(_)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate derivedFluentFlowPush(
|
||||
SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg,
|
||||
SummaryComponent head, SummaryComponentStack tail, int i
|
||||
) {
|
||||
derivedFluentFlow(c, input, arg, tail, _) and
|
||||
head = arg.drop(i).head() and
|
||||
i = arg.length() - 2
|
||||
or
|
||||
exists(SummaryComponent head0, SummaryComponentStack tail0 |
|
||||
derivedFluentFlowPush(c, input, arg, head0, tail0, i + 1) and
|
||||
head = arg.drop(i).head() and
|
||||
tail = SummaryComponentStack::push(head0, tail0)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isCallbackParameter(SummaryComponentStack s) {
|
||||
s.head() = TParameterSummaryComponent(_) and exists(s.tail())
|
||||
}
|
||||
|
||||
private predicate isContentOfArgument(SummaryComponentStack s) {
|
||||
s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail())
|
||||
private predicate isContentOfArgument(SummaryComponentStack s, int i) {
|
||||
s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail(), i)
|
||||
or
|
||||
s = TSingletonSummaryComponentStack(TArgumentSummaryComponent(_))
|
||||
s = TSingletonSummaryComponentStack(TArgumentSummaryComponent(i))
|
||||
}
|
||||
|
||||
private predicate outputState(SummarizedCallable c, SummaryComponentStack s) {
|
||||
@@ -508,9 +578,14 @@ module Private {
|
||||
* node, and back out to `p`.
|
||||
*/
|
||||
predicate summaryAllowParameterReturnInSelf(ParamNode p) {
|
||||
exists(SummarizedCallable c, int i |
|
||||
c.clearsContent(i, _) and
|
||||
p.isParameterOf(c, i)
|
||||
exists(SummarizedCallable c, int i | p.isParameterOf(c, i) |
|
||||
c.clearsContent(i, _)
|
||||
or
|
||||
exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents |
|
||||
summary(c, inputContents, outputContents, _) and
|
||||
inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(i)) and
|
||||
outputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(i))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -534,22 +609,6 @@ module Private {
|
||||
preservesValue = false and not summary(c, inputContents, outputContents, true)
|
||||
)
|
||||
or
|
||||
// If flow through a method updates a parameter from some input A, and that
|
||||
// parameter also is returned through B, then we'd like a combined flow from A
|
||||
// to B as well. As an example, this simplifies modeling of fluent methods:
|
||||
// for `StringBuilder.append(x)` with a specified value flow from qualifier to
|
||||
// return value and taint flow from argument 0 to the qualifier, then this
|
||||
// allows us to infer taint flow from argument 0 to the return value.
|
||||
succ instanceof ParamNode and
|
||||
summaryPostUpdateNode(pred, succ) and
|
||||
preservesValue = true
|
||||
or
|
||||
// Similarly we would like to chain together summaries where values get passed
|
||||
// into callbacks along the way.
|
||||
pred instanceof ArgNode and
|
||||
summaryPostUpdateNode(succ, pred) and
|
||||
preservesValue = true
|
||||
or
|
||||
exists(SummarizedCallable c, int i |
|
||||
pred.(ParamNode).isParameterOf(c, i) and
|
||||
succ = summaryNode(c, TSummaryNodeClearsContentState(i, _)) and
|
||||
|
||||
@@ -141,25 +141,23 @@ private module Liveness {
|
||||
|
||||
private import Liveness
|
||||
|
||||
/** Holds if `bb1` strictly dominates `bb2`. */
|
||||
private predicate strictlyDominates(BasicBlock bb1, BasicBlock bb2) {
|
||||
bb1 = getImmediateBasicBlockDominator+(bb2)
|
||||
}
|
||||
|
||||
/** Holds if `bb1` dominates a predecessor of `bb2`. */
|
||||
private predicate dominatesPredecessor(BasicBlock bb1, BasicBlock bb2) {
|
||||
exists(BasicBlock pred | pred = getABasicBlockPredecessor(bb2) |
|
||||
bb1 = pred
|
||||
or
|
||||
strictlyDominates(bb1, pred)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `df` is in the dominance frontier of `bb`. */
|
||||
pragma[noinline]
|
||||
/**
|
||||
* Holds if `df` is in the dominance frontier of `bb`.
|
||||
*
|
||||
* This is equivalent to:
|
||||
*
|
||||
* ```ql
|
||||
* bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and
|
||||
* not bb = getImmediateBasicBlockDominator+(df)
|
||||
* ```
|
||||
*/
|
||||
private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) {
|
||||
dominatesPredecessor(bb, df) and
|
||||
not strictlyDominates(bb, df)
|
||||
bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df)
|
||||
or
|
||||
exists(BasicBlock prev | inDominanceFrontier(prev, df) |
|
||||
bb = getImmediateBasicBlockDominator(prev) and
|
||||
not bb = getImmediateBasicBlockDominator(df)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -636,3 +634,28 @@ class UncertainWriteDefinition extends WriteDefinition {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a set of consistency queries. */
|
||||
module Consistency {
|
||||
abstract class RelevantDefinition extends Definition {
|
||||
abstract predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
);
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) {
|
||||
variableRead(bb, i, v, _) and
|
||||
not ssaDefReachesRead(_, _, bb, i)
|
||||
}
|
||||
|
||||
query predicate deadDef(RelevantDefinition def, SourceVariable v) {
|
||||
v = def.getSourceVariable() and
|
||||
not ssaDefReachesRead(_, def, _, _) and
|
||||
not phiHasInputFromBlock(_, def, _)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@ private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
|
||||
/** Defines calls to `ActiveStorage::Filename#sanitized` as path sanitizers. */
|
||||
/** A call to `ActiveStorage::Filename#sanitized`, considered as a path sanitizer. */
|
||||
class ActiveStorageFilenameSanitizedCall extends Path::PathSanitization::Range, DataFlow::CallNode {
|
||||
ActiveStorageFilenameSanitizedCall() {
|
||||
this.getReceiver() =
|
||||
API::getTopLevelMember("ActiveStorage").getMember("Filename").getAnInstantiation() and
|
||||
this.asExpr().getExpr().(MethodCall).getMethodName() = "sanitized"
|
||||
this.getMethodName() = "sanitized"
|
||||
}
|
||||
}
|
||||
|
||||
/** Taint summary for `ActiveStorage::Filename.new`. */
|
||||
/** The taint summary for `ActiveStorage::Filename.new`. */
|
||||
class ActiveStorageFilenameNewSummary extends SummarizedCallable {
|
||||
ActiveStorageFilenameNewSummary() { this = "ActiveStorage::Filename.new" }
|
||||
|
||||
@@ -33,7 +33,7 @@ class ActiveStorageFilenameNewSummary extends SummarizedCallable {
|
||||
}
|
||||
}
|
||||
|
||||
/** Taint summary for `ActiveStorage::Filename#sanitized`. */
|
||||
/** The taint summary for `ActiveStorage::Filename#sanitized`. */
|
||||
class ActiveStorageFilenameSanitizedSummary extends SummarizedCallable {
|
||||
ActiveStorageFilenameSanitizedSummary() { this = "ActiveStorage::Filename#sanitized" }
|
||||
|
||||
|
||||
@@ -127,8 +127,7 @@ module IO {
|
||||
api = "IO" and
|
||||
exists(IOInstanceStrict ii |
|
||||
this.getReceiver() = ii and
|
||||
this.asExpr().getExpr().(MethodCall).getMethodName() =
|
||||
ioFileReaderMethodName(classMethodCall)
|
||||
this.getMethodName() = ioFileReaderMethodName(classMethodCall)
|
||||
)
|
||||
or
|
||||
// File instance methods
|
||||
@@ -136,8 +135,7 @@ module IO {
|
||||
api = "File" and
|
||||
exists(File::FileInstance fi |
|
||||
this.getReceiver() = fi and
|
||||
this.asExpr().getExpr().(MethodCall).getMethodName() =
|
||||
ioFileReaderMethodName(classMethodCall)
|
||||
this.getMethodName() = ioFileReaderMethodName(classMethodCall)
|
||||
)
|
||||
// TODO: enumeration style methods such as `each`, `foreach`, etc.
|
||||
}
|
||||
@@ -232,7 +230,7 @@ module File {
|
||||
// Instance methods
|
||||
exists(FileInstance fi |
|
||||
this.getReceiver() = fi and
|
||||
this.asExpr().getExpr().(MethodCall).getMethodName() = ["path", "to_path"]
|
||||
this.getMethodName() = ["path", "to_path"]
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -256,7 +254,7 @@ module File {
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow summary for several methods on the `File` class that propagate taint
|
||||
* A flow summary for several methods on the `File` class that propagate taint
|
||||
* from their first argument to the return value.
|
||||
*/
|
||||
class FilePathConversionSummary extends SummarizedCallable {
|
||||
@@ -279,7 +277,7 @@ module File {
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow summary for `File.join`, which propagates taint from every argument to
|
||||
* A flow summary for `File.join`, which propagates taint from every argument to
|
||||
* its return value.
|
||||
*/
|
||||
class FileJoinSummary extends SummarizedCallable {
|
||||
|
||||
@@ -27,8 +27,6 @@ class KernelMethodCall extends DataFlow::CallNode {
|
||||
)
|
||||
}
|
||||
|
||||
string getMethodName() { result = methodCall.getMethodName() }
|
||||
|
||||
int getNumberOfArguments() { result = methodCall.getNumberOfArguments() }
|
||||
}
|
||||
|
||||
@@ -74,7 +72,7 @@ string basicObjectInstanceMethodName() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance methods on `BasicObject`, which are available to all classes.
|
||||
* An instance method on `BasicObject`, which is available to all classes.
|
||||
*/
|
||||
class BasicObjectInstanceMethodCall extends UnknownMethodCall {
|
||||
BasicObjectInstanceMethodCall() { this.getMethodName() = basicObjectInstanceMethodName() }
|
||||
@@ -95,14 +93,14 @@ string objectInstanceMethodName() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance methods on `Object`, which are available to all classes except `BasicObject`.
|
||||
* An instance method on `Object`, which is available to all classes except `BasicObject`.
|
||||
*/
|
||||
class ObjectInstanceMethodCall extends UnknownMethodCall {
|
||||
ObjectInstanceMethodCall() { this.getMethodName() = objectInstanceMethodName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Method calls which have no known target.
|
||||
* A `Method` call that has no known target.
|
||||
* These will typically be calls to methods inherited from a superclass.
|
||||
*/
|
||||
class UnknownMethodCall extends MethodCall {
|
||||
|
||||
@@ -18,12 +18,14 @@ private import codeql.ruby.ApiGraphs
|
||||
* https://github.com/excon/excon/blob/master/README.md
|
||||
*/
|
||||
class ExconHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node requestUse;
|
||||
DataFlow::CallNode requestUse;
|
||||
API::Node requestNode;
|
||||
API::Node connectionNode;
|
||||
DataFlow::Node connectionUse;
|
||||
|
||||
ExconHttpRequest() {
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
connectionUse = connectionNode.getAnImmediateUse() and
|
||||
connectionNode =
|
||||
[
|
||||
// one-off requests
|
||||
@@ -44,6 +46,17 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||
|
||||
override DataFlow::Node getURL() {
|
||||
// For one-off requests, the URL is in the first argument of the request method call.
|
||||
// For connection re-use, the URL is split between the first argument of the `new` call
|
||||
// and the `path` keyword argument of the request method call.
|
||||
result = requestUse.getArgument(0) and not result.asExpr().getExpr() instanceof Pair
|
||||
or
|
||||
result = requestUse.getKeywordArgument("path")
|
||||
or
|
||||
result = connectionUse.(DataFlow::CallNode).getArgument(0)
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
// Check for `ssl_verify_peer: false` in the options hash.
|
||||
exists(DataFlow::Node arg, int i |
|
||||
|
||||
@@ -11,12 +11,16 @@ private import codeql.ruby.ApiGraphs
|
||||
* # connection re-use
|
||||
* connection = Faraday.new("http://example.com")
|
||||
* connection.get("/").body
|
||||
*
|
||||
* connection = Faraday.new(url: "http://example.com")
|
||||
* connection.get("/").body
|
||||
* ```
|
||||
*/
|
||||
class FaradayHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node requestUse;
|
||||
API::Node requestNode;
|
||||
API::Node connectionNode;
|
||||
DataFlow::Node connectionUse;
|
||||
DataFlow::CallNode requestUse;
|
||||
|
||||
FaradayHttpRequest() {
|
||||
connectionNode =
|
||||
@@ -29,11 +33,18 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range {
|
||||
requestNode =
|
||||
connectionNode.getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
connectionUse = connectionNode.getAnImmediateUse() and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||
|
||||
override DataFlow::Node getURL() {
|
||||
result = requestUse.getArgument(0) or
|
||||
result = connectionUse.(DataFlow::CallNode).getArgument(0) or
|
||||
result = connectionUse.(DataFlow::CallNode).getKeywordArgument("url")
|
||||
}
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
// `Faraday::new` takes an options hash as its second argument, and we're
|
||||
// looking for
|
||||
|
||||
@@ -12,7 +12,7 @@ private import codeql.ruby.ApiGraphs
|
||||
class HttpClientRequest extends HTTP::Client::Request::Range {
|
||||
API::Node requestNode;
|
||||
API::Node connectionNode;
|
||||
DataFlow::Node requestUse;
|
||||
DataFlow::CallNode requestUse;
|
||||
string method;
|
||||
|
||||
HttpClientRequest() {
|
||||
@@ -31,6 +31,8 @@ class HttpClientRequest extends HTTP::Client::Request::Range {
|
||||
this = requestUse.asExpr().getExpr()
|
||||
}
|
||||
|
||||
override DataFlow::Node getURL() { result = requestUse.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getResponseBody() {
|
||||
// The `get_content` and `post_content` methods return the response body as
|
||||
// a string. The other methods return a `HTTPClient::Message` object which
|
||||
|
||||
@@ -11,6 +11,7 @@ private import codeql.ruby.ApiGraphs
|
||||
* # TODO: module inclusion
|
||||
* class MyClass
|
||||
* include HTTParty
|
||||
* base_uri "http://example.com"
|
||||
* end
|
||||
*
|
||||
* MyClass.new("http://example.com")
|
||||
@@ -18,7 +19,7 @@ private import codeql.ruby.ApiGraphs
|
||||
*/
|
||||
class HttpartyRequest extends HTTP::Client::Request::Range {
|
||||
API::Node requestNode;
|
||||
DataFlow::Node requestUse;
|
||||
DataFlow::CallNode requestUse;
|
||||
|
||||
HttpartyRequest() {
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
@@ -28,6 +29,8 @@ class HttpartyRequest extends HTTP::Client::Request::Range {
|
||||
this = requestUse.asExpr().getExpr()
|
||||
}
|
||||
|
||||
override DataFlow::Node getURL() { result = requestUse.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getResponseBody() {
|
||||
// If HTTParty can recognise the response type, it will parse and return it
|
||||
// directly from the request call. Otherwise, it will return a `HTTParty::Response`
|
||||
|
||||
@@ -46,7 +46,7 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
|
||||
* Gets the node representing the URL of the request.
|
||||
* Currently unused, but may be useful in future, e.g. to filter out certain requests.
|
||||
*/
|
||||
DataFlow::Node getURLArgument() { result = request.getArgument(0) }
|
||||
override DataFlow::Node getURL() { result = request.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ private import codeql.ruby.frameworks.StandardLibrary
|
||||
*/
|
||||
class OpenUriRequest extends HTTP::Client::Request::Range {
|
||||
API::Node requestNode;
|
||||
DataFlow::Node requestUse;
|
||||
DataFlow::CallNode requestUse;
|
||||
|
||||
OpenUriRequest() {
|
||||
requestNode =
|
||||
@@ -24,6 +24,8 @@ class OpenUriRequest extends HTTP::Client::Request::Range {
|
||||
this = requestUse.asExpr().getExpr()
|
||||
}
|
||||
|
||||
override DataFlow::Node getURL() { result = requestUse.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getResponseBody() {
|
||||
result = requestNode.getAMethodCall(["read", "readlines"])
|
||||
}
|
||||
@@ -48,7 +50,7 @@ class OpenUriRequest extends HTTP::Client::Request::Range {
|
||||
* ```
|
||||
*/
|
||||
class OpenUriKernelOpenRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node requestUse;
|
||||
DataFlow::CallNode requestUse;
|
||||
|
||||
OpenUriKernelOpenRequest() {
|
||||
requestUse instanceof KernelMethodCall and
|
||||
@@ -56,6 +58,8 @@ class OpenUriKernelOpenRequest extends HTTP::Client::Request::Range {
|
||||
this = requestUse.asExpr().getExpr()
|
||||
}
|
||||
|
||||
override DataFlow::Node getURL() { result = requestUse.getArgument(0) }
|
||||
|
||||
override DataFlow::CallNode getResponseBody() {
|
||||
result.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and
|
||||
requestUse.(DataFlow::LocalSourceNode).flowsTo(result.getReceiver())
|
||||
|
||||
@@ -6,23 +6,38 @@ private import codeql.ruby.ApiGraphs
|
||||
* A call that makes an HTTP request using `RestClient`.
|
||||
* ```ruby
|
||||
* RestClient.get("http://example.com").body
|
||||
* RestClient::Resource.new("http://example.com").get.body
|
||||
* RestClient::Request.execute(url: "http://example.com").body
|
||||
* ```
|
||||
*/
|
||||
class RestClientHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node requestUse;
|
||||
DataFlow::CallNode requestUse;
|
||||
API::Node requestNode;
|
||||
API::Node connectionNode;
|
||||
|
||||
RestClientHttpRequest() {
|
||||
connectionNode =
|
||||
[
|
||||
API::getTopLevelMember("RestClient"),
|
||||
API::getTopLevelMember("RestClient").getMember("Resource").getInstance()
|
||||
] and
|
||||
requestNode =
|
||||
connectionNode.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
||||
requestUse = requestNode.getAnImmediateUse() and
|
||||
this = requestUse.asExpr().getExpr()
|
||||
this = requestUse.asExpr().getExpr() and
|
||||
(
|
||||
connectionNode =
|
||||
[
|
||||
API::getTopLevelMember("RestClient"),
|
||||
API::getTopLevelMember("RestClient").getMember("Resource").getInstance()
|
||||
] and
|
||||
requestNode =
|
||||
connectionNode.getReturn(["get", "head", "delete", "options", "post", "put", "patch"])
|
||||
or
|
||||
connectionNode = API::getTopLevelMember("RestClient").getMember("Request") and
|
||||
requestNode = connectionNode.getReturn("execute")
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getURL() {
|
||||
result = requestUse.getKeywordArgument("url")
|
||||
or
|
||||
result = requestUse.getArgument(0) and
|
||||
// this rules out the alternative above
|
||||
not result.asExpr().getExpr() instanceof Pair
|
||||
}
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||
|
||||
@@ -9,7 +9,7 @@ private import codeql.ruby.ApiGraphs
|
||||
* ```
|
||||
*/
|
||||
class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
|
||||
DataFlow::Node requestUse;
|
||||
DataFlow::CallNode requestUse;
|
||||
API::Node requestNode;
|
||||
|
||||
TyphoeusHttpRequest() {
|
||||
@@ -20,6 +20,8 @@ class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
|
||||
this = requestUse.asExpr().getExpr()
|
||||
}
|
||||
|
||||
override DataFlow::Node getURL() { result = requestUse.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||
|
||||
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
private import AST
|
||||
private import codeql.ruby.regexp.RegExpTreeView as RETV
|
||||
private import codeql.ruby.security.performance.RegExpTreeView as RETV
|
||||
|
||||
/** Holds if `n` appears in the desugaring of some other node. */
|
||||
predicate isDesugared(AstNode n) {
|
||||
|
||||
306
ruby/ql/lib/codeql/ruby/security/BadTagFilterQuery.qll
Normal file
306
ruby/ql/lib/codeql/ruby/security/BadTagFilterQuery.qll
Normal file
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* Provides precicates for reasoning about bad tag filter vulnerabilities.
|
||||
*/
|
||||
|
||||
import performance.ReDoSUtil
|
||||
|
||||
/**
|
||||
* A module for determining if a regexp matches a given string,
|
||||
* and reasoning about which capture groups are filled by a given string.
|
||||
*/
|
||||
private module RegexpMatching {
|
||||
/**
|
||||
* A class to test whether a regular expression matches a string.
|
||||
* Override this class and extend `test`/`testWithGroups` to configure which strings should be tested for acceptance by this regular expression.
|
||||
* The result can afterwards be read from the `matches` predicate.
|
||||
*
|
||||
* Strings in the `testWithGroups` predicate are also tested for which capture groups are filled by the given string.
|
||||
* The result is available in the `fillCaptureGroup` predicate.
|
||||
*/
|
||||
abstract class MatchedRegExp extends RegExpTerm {
|
||||
MatchedRegExp() { this.isRootTerm() }
|
||||
|
||||
/**
|
||||
* Holds if it should be tested whether this regular expression matches `str`.
|
||||
*
|
||||
* If `ignorePrefix` is true, then a regexp without a start anchor will be treated as if it had a start anchor.
|
||||
* E.g. a regular expression `/foo$/` will match any string that ends with "foo",
|
||||
* but if `ignorePrefix` is true, it will only match "foo".
|
||||
*/
|
||||
predicate test(string str, boolean ignorePrefix) {
|
||||
none() // maybe overriden in subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `test(..)`, but where the `fillsCaptureGroup` afterwards tells which capture groups were filled by the given string.
|
||||
*/
|
||||
predicate testWithGroups(string str, boolean ignorePrefix) {
|
||||
none() // maybe overriden in subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this RegExp matches `str`, where `str` is either in the `test` or `testWithGroups` predicate.
|
||||
*/
|
||||
final predicate matches(string str) {
|
||||
exists(State state | state = getAState(this, str.length() - 1, str, _) |
|
||||
epsilonSucc*(state) = Accept(_)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if matching `str` may fill capture group number `g`.
|
||||
* Only holds if `str` is in the `testWithGroups` predicate.
|
||||
*/
|
||||
final predicate fillsCaptureGroup(string str, int g) {
|
||||
exists(State s |
|
||||
s = getAStateThatReachesAccept(this, _, str, _) and
|
||||
g = group(s.getRepr())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a state the regular expression `reg` can be in after matching the `i`th char in `str`.
|
||||
* The regular expression is modelled as a non-determistic finite automaton,
|
||||
* the regular expression can therefore be in multiple states after matching a character.
|
||||
*
|
||||
* It's a forward search to all possible states, and there is thus no guarantee that the state is on a path to an accepting state.
|
||||
*/
|
||||
private State getAState(MatchedRegExp reg, int i, string str, boolean ignorePrefix) {
|
||||
// start state, the -1 position before any chars have been matched
|
||||
i = -1 and
|
||||
(
|
||||
reg.test(str, ignorePrefix)
|
||||
or
|
||||
reg.testWithGroups(str, ignorePrefix)
|
||||
) and
|
||||
result.getRepr().getRootTerm() = reg and
|
||||
isStartState(result)
|
||||
or
|
||||
// recursive case
|
||||
result = getAStateAfterMatching(reg, _, str, i, _, ignorePrefix)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next state after the `prev` state from `reg`.
|
||||
* `prev` is the state after matching `fromIndex` chars in `str`,
|
||||
* and the result is the state after matching `toIndex` chars in `str`.
|
||||
*
|
||||
* This predicate is used as a step relation in the forwards search (`getAState`),
|
||||
* and also as a step relation in the later backwards search (`getAStateThatReachesAccept`).
|
||||
*/
|
||||
private State getAStateAfterMatching(
|
||||
MatchedRegExp reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
|
||||
) {
|
||||
// the basic recursive case - outlined into a noopt helper to make performance work out.
|
||||
result = getAStateAfterMatchingAux(reg, prev, str, toIndex, fromIndex, ignorePrefix)
|
||||
or
|
||||
// we can skip past word boundaries if the next char is a non-word char.
|
||||
fromIndex = toIndex and
|
||||
prev.getRepr() instanceof RegExpWordBoundary and
|
||||
prev = getAState(reg, toIndex, str, ignorePrefix) and
|
||||
after(prev.getRepr()) = result and
|
||||
str.charAt(toIndex + 1).regexpMatch("\\W") // \W matches any non-word char.
|
||||
}
|
||||
|
||||
pragma[noopt]
|
||||
private State getAStateAfterMatchingAux(
|
||||
MatchedRegExp reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
|
||||
) {
|
||||
prev = getAState(reg, fromIndex, str, ignorePrefix) and
|
||||
fromIndex = toIndex - 1 and
|
||||
exists(string char | char = str.charAt(toIndex) | specializedDeltaClosed(prev, char, result)) and
|
||||
not discardedPrefixStep(prev, result, ignorePrefix)
|
||||
}
|
||||
|
||||
/** Holds if a step from `prev` to `next` should be discarded when the `ignorePrefix` flag is set. */
|
||||
private predicate discardedPrefixStep(State prev, State next, boolean ignorePrefix) {
|
||||
prev = mkMatch(any(RegExpRoot r)) and
|
||||
ignorePrefix = true and
|
||||
next = prev
|
||||
}
|
||||
|
||||
// The `deltaClosed` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
|
||||
private predicate specializedDeltaClosed(State prev, string char, State next) {
|
||||
deltaClosed(prev, specializedGetAnInputSymbolMatching(char), next)
|
||||
}
|
||||
|
||||
// The `getAnInputSymbolMatching` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
|
||||
pragma[noinline]
|
||||
private InputSymbol specializedGetAnInputSymbolMatching(string char) {
|
||||
exists(string s, MatchedRegExp r |
|
||||
r.test(s, _)
|
||||
or
|
||||
r.testWithGroups(s, _)
|
||||
|
|
||||
char = s.charAt(_)
|
||||
) and
|
||||
result = getAnInputSymbolMatching(char)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th state on a path to the accepting state when `reg` matches `str`.
|
||||
* Starts with an accepting state as found by `getAState` and searches backwards
|
||||
* to the start state through the reachable states (as found by `getAState`).
|
||||
*
|
||||
* This predicate holds the invariant that the result state can be reached with `i` steps from a start state,
|
||||
* and an accepting state can be found after (`str.length() - 1 - i`) steps from the result.
|
||||
* The result state is therefore always on a valid path where `reg` accepts `str`.
|
||||
*
|
||||
* This predicate is only used to find which capture groups a regular expression has filled,
|
||||
* and thus the search is only performed for the strings in the `testWithGroups(..)` predicate.
|
||||
*/
|
||||
private State getAStateThatReachesAccept(
|
||||
MatchedRegExp reg, int i, string str, boolean ignorePrefix
|
||||
) {
|
||||
// base case, reaches an accepting state from the last state in `getAState(..)`
|
||||
reg.testWithGroups(str, ignorePrefix) and
|
||||
i = str.length() - 1 and
|
||||
result = getAState(reg, i, str, ignorePrefix) and
|
||||
epsilonSucc*(result) = Accept(_)
|
||||
or
|
||||
// recursive case. `next` is the next state to be matched after matching `prev`.
|
||||
// this predicate is doing a backwards search, so `prev` is the result we are looking for.
|
||||
exists(State next, State prev, int fromIndex, int toIndex |
|
||||
next = getAStateThatReachesAccept(reg, toIndex, str, ignorePrefix) and
|
||||
next = getAStateAfterMatching(reg, prev, str, toIndex, fromIndex, ignorePrefix) and
|
||||
i = fromIndex and
|
||||
result = prev
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the capture group number that `term` belongs to. */
|
||||
private int group(RegExpTerm term) {
|
||||
exists(RegExpGroup grp | grp.getNumber() = result | term.getParent*() = grp)
|
||||
}
|
||||
}
|
||||
|
||||
/** A class to test whether a regular expression matches certain HTML tags. */
|
||||
class HTMLMatchingRegExp extends RegexpMatching::MatchedRegExp {
|
||||
HTMLMatchingRegExp() {
|
||||
// the regexp must mention "<" and ">" explicitly.
|
||||
forall(string angleBracket | angleBracket = ["<", ">"] |
|
||||
any(RegExpConstant term | term.getValue().matches("%" + angleBracket + "%")).getRootTerm() =
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
override predicate testWithGroups(string str, boolean ignorePrefix) {
|
||||
ignorePrefix = true and
|
||||
str = ["<!-- foo -->", "<!-- foo --!>", "<!- foo ->", "<foo>", "<script>"]
|
||||
}
|
||||
|
||||
override predicate test(string str, boolean ignorePrefix) {
|
||||
ignorePrefix = true and
|
||||
str =
|
||||
[
|
||||
"<!-- foo -->", "<!- foo ->", "<!-- foo --!>", "<!-- foo\n -->", "<script>foo</script>",
|
||||
"<script \n>foo</script>", "<script >foo\n</script>", "<foo ></foo>", "<foo>",
|
||||
"<foo src=\"foo\"></foo>", "<script>", "<script src=\"foo\"></script>",
|
||||
"<script src='foo'></script>", "<SCRIPT>foo</SCRIPT>", "<script\tsrc=\"foo\"/>",
|
||||
"<script\tsrc='foo'></script>", "<sCrIpT>foo</ScRiPt>", "<script src=\"foo\">foo</script >",
|
||||
"<script src=\"foo\">foo</script foo=\"bar\">", "<script src=\"foo\">foo</script\t\n bar>"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `regexp` matches some HTML tags, but misses some HTML tags that it should match.
|
||||
*
|
||||
* When adding a new case to this predicate, make sure the test string used in `matches(..)` calls are present in `HTMLMatchingRegExp::test` / `HTMLMatchingRegExp::testWithGroups`.
|
||||
*/
|
||||
predicate isBadRegexpFilter(HTMLMatchingRegExp regexp, string msg) {
|
||||
// CVE-2021-33829 - matching both "<!-- foo -->" and "<!-- foo --!>", but in different capture groups
|
||||
regexp.matches("<!-- foo -->") and
|
||||
regexp.matches("<!-- foo --!>") and
|
||||
exists(int a, int b | a != b |
|
||||
regexp.fillsCaptureGroup("<!-- foo -->", a) and
|
||||
// <!-- foo --> might be ambigously parsed (matching both capture groups), and that is ok here.
|
||||
regexp.fillsCaptureGroup("<!-- foo --!>", b) and
|
||||
not regexp.fillsCaptureGroup("<!-- foo --!>", a) and
|
||||
msg =
|
||||
"Comments ending with --> are matched differently from comments ending with --!>. The first is matched with capture group "
|
||||
+ a + " and comments ending with --!> are matched with capture group " +
|
||||
strictconcat(int i | regexp.fillsCaptureGroup("<!-- foo --!>", i) | i.toString(), ", ") +
|
||||
"."
|
||||
)
|
||||
or
|
||||
// CVE-2020-17480 - matching "<!-- foo -->" and other tags, but not "<!-- foo --!>".
|
||||
exists(int group, int other |
|
||||
group != other and
|
||||
regexp.fillsCaptureGroup("<!-- foo -->", group) and
|
||||
regexp.fillsCaptureGroup("<foo>", other) and
|
||||
not regexp.matches("<!-- foo --!>") and
|
||||
not regexp.fillsCaptureGroup("<!-- foo -->", any(int i | i != group)) and
|
||||
not regexp.fillsCaptureGroup("<!- foo ->", group) and
|
||||
not regexp.fillsCaptureGroup("<foo>", group) and
|
||||
not regexp.fillsCaptureGroup("<script>", group) and
|
||||
msg =
|
||||
"This regular expression only parses --> (capture group " + group +
|
||||
") and not --!> as a HTML comment end tag."
|
||||
)
|
||||
or
|
||||
regexp.matches("<!-- foo -->") and
|
||||
not regexp.matches("<!-- foo\n -->") and
|
||||
not regexp.matches("<!- foo ->") and
|
||||
not regexp.matches("<foo>") and
|
||||
not regexp.matches("<script>") and
|
||||
msg = "This regular expression does not match comments containing newlines."
|
||||
or
|
||||
regexp.matches("<script>foo</script>") and
|
||||
regexp.matches("<script src=\"foo\"></script>") and
|
||||
not regexp.matches("<foo ></foo>") and
|
||||
(
|
||||
not regexp.matches("<script \n>foo</script>") and
|
||||
msg = "This regular expression matches <script></script>, but not <script \\n></script>"
|
||||
or
|
||||
not regexp.matches("<script >foo\n</script>") and
|
||||
msg = "This regular expression matches <script>...</script>, but not <script >...\\n</script>"
|
||||
)
|
||||
or
|
||||
regexp.matches("<script>foo</script>") and
|
||||
regexp.matches("<script src=\"foo\"></script>") and
|
||||
not regexp.matches("<script src='foo'></script>") and
|
||||
not regexp.matches("<foo>") and
|
||||
msg = "This regular expression does not match script tags where the attribute uses single-quotes."
|
||||
or
|
||||
regexp.matches("<script>foo</script>") and
|
||||
regexp.matches("<script src='foo'></script>") and
|
||||
not regexp.matches("<script src=\"foo\"></script>") and
|
||||
not regexp.matches("<foo>") and
|
||||
msg = "This regular expression does not match script tags where the attribute uses double-quotes."
|
||||
or
|
||||
regexp.matches("<script>foo</script>") and
|
||||
regexp.matches("<script src='foo'></script>") and
|
||||
not regexp.matches("<script\tsrc='foo'></script>") and
|
||||
not regexp.matches("<foo>") and
|
||||
not regexp.matches("<foo src=\"foo\"></foo>") and
|
||||
msg = "This regular expression does not match script tags where tabs are used between attributes."
|
||||
or
|
||||
regexp.matches("<script>foo</script>") and
|
||||
not RegExpFlags::isIgnoreCase(regexp) and
|
||||
not regexp.matches("<foo>") and
|
||||
not regexp.matches("<foo ></foo>") and
|
||||
(
|
||||
not regexp.matches("<SCRIPT>foo</SCRIPT>") and
|
||||
msg = "This regular expression does not match upper case <SCRIPT> tags."
|
||||
or
|
||||
not regexp.matches("<sCrIpT>foo</ScRiPt>") and
|
||||
regexp.matches("<SCRIPT>foo</SCRIPT>") and
|
||||
msg = "This regular expression does not match mixed case <sCrIpT> tags."
|
||||
)
|
||||
or
|
||||
regexp.matches("<script src=\"foo\"></script>") and
|
||||
not regexp.matches("<foo>") and
|
||||
not regexp.matches("<foo ></foo>") and
|
||||
(
|
||||
not regexp.matches("<script src=\"foo\">foo</script >") and
|
||||
msg = "This regular expression does not match script end tags like </script >."
|
||||
or
|
||||
not regexp.matches("<script src=\"foo\">foo</script foo=\"bar\">") and
|
||||
msg = "This regular expression does not match script end tags like </script foo=\"bar\">."
|
||||
or
|
||||
not regexp.matches("<script src=\"foo\">foo</script\t\n bar>") and
|
||||
msg = "This regular expression does not match script end tags like </script\\t\\n bar>."
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about
|
||||
* server side request forgery, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.dataflow.Sanitizers
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about
|
||||
* server side request forgery, as well as extension points for adding your own.
|
||||
*/
|
||||
module ServerSideRequestForgery {
|
||||
/**
|
||||
* A data flow source for server side request forgery vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for server side request forgery vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for server side request forgery vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer guard for "URL redirection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/** A source of remote user input, considered as a flow source for server side request forgery. */
|
||||
class RemoteFlowSourceAsSource extends Source {
|
||||
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
|
||||
}
|
||||
|
||||
/** The URL of an HTTP request, considered as a sink. */
|
||||
class HttpRequestAsSink extends Sink {
|
||||
HttpRequestAsSink() { exists(HTTP::Client::Request req | req.getURL() = this) }
|
||||
}
|
||||
|
||||
/** String interpolation with a fixed prefix, considered as a flow sanitizer. */
|
||||
class StringInterpolationAsSanitizer extends PrefixedStringInterpolation, Sanitizer { }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting
|
||||
* "Server side request forgery" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is needed,
|
||||
* otherwise `ServerSideRequestForgeryCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import codeql.ruby.DataFlow::DataFlow::PathGraph
|
||||
import codeql.ruby.DataFlow
|
||||
import codeql.ruby.TaintTracking
|
||||
import ServerSideRequestForgeryCustomizations::ServerSideRequestForgery
|
||||
import codeql.ruby.dataflow.BarrierGuards
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting
|
||||
* "Server side request forgery" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ServerSideRequestForgery" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard or
|
||||
guard instanceof StringConstCompare or
|
||||
guard instanceof StringConstArrayInclusionCall
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.dataflow.BarrierGuards
|
||||
private import codeql.ruby.dataflow.Sanitizers
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
@@ -57,9 +58,9 @@ module UrlRedirect {
|
||||
this = e.getRedirectLocation() and
|
||||
// As a rough heuristic, assume that methods with these names are handlers for POST/PUT/PATCH/DELETE requests,
|
||||
// which are not as vulnerable to URL redirection because browsers will not initiate them from clicking a link.
|
||||
not this.getEnclosingCallable()
|
||||
.asCallable()
|
||||
.(Method)
|
||||
not this.asExpr()
|
||||
.getExpr()
|
||||
.getEnclosingMethod()
|
||||
.getName()
|
||||
.regexpMatch(".*(create|update|destroy).*")
|
||||
)
|
||||
@@ -85,6 +86,8 @@ module UrlRedirect {
|
||||
}
|
||||
|
||||
/**
|
||||
* A string interpolation, seen as a sanitizer for "URL redirection" vulnerabilities.
|
||||
*
|
||||
* String interpolation is considered safe, provided the string is prefixed by a non-tainted value.
|
||||
* In most cases this will prevent the tainted value from controlling e.g. the host of the URL.
|
||||
*
|
||||
@@ -103,11 +106,7 @@ module UrlRedirect {
|
||||
*
|
||||
* We currently don't catch these cases.
|
||||
*/
|
||||
class StringInterpolationAsSanitizer extends Sanitizer {
|
||||
StringInterpolationAsSanitizer() {
|
||||
exists(StringlikeLiteral str, int n | str.getComponent(n) = this.asExpr().getExpr() and n > 0)
|
||||
}
|
||||
}
|
||||
class StringInterpolationAsSanitizer extends PrefixedStringInterpolation, Sanitizer { }
|
||||
|
||||
/**
|
||||
* These methods return a new `ActionController::Parameters` or a `Hash` containing a subset of
|
||||
|
||||
@@ -8,9 +8,9 @@ private import codeql.ruby.AST as AST
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.regexp.ParseRegExp as RegExp
|
||||
private import codeql.ruby.regexp.RegExpTreeView
|
||||
private import codeql.ruby.regexp.SuperlinearBackTracking
|
||||
private import codeql.ruby.security.performance.ParseRegExp as RegExp
|
||||
private import codeql.ruby.security.performance.RegExpTreeView
|
||||
private import codeql.ruby.security.performance.SuperlinearBackTracking
|
||||
|
||||
module PolynomialReDoS {
|
||||
/**
|
||||
@@ -140,12 +140,10 @@ class RegExpRoot extends RegExpTerm {
|
||||
predicate isRelevant() {
|
||||
// there is at least one repetition
|
||||
getRoot(any(InfiniteRepetitionQuantifier q)) = this and
|
||||
// there are no lookbehinds
|
||||
not exists(RegExpLookbehind lbh | getRoot(lbh) = this) and
|
||||
// is actually used as a RegExp
|
||||
this.isUsedAsRegExp() //and
|
||||
// // pragmatic performance optimization: ignore minified files.
|
||||
// not getRootTerm().getParent().(Expr).getTopLevel().isMinified()
|
||||
isUsedAsRegExp() and
|
||||
// not excluded for library specific reasons
|
||||
not isExcluded(getRootTerm().getParent())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,38 +154,68 @@ private class RegexpCharacterConstant extends RegExpConstant {
|
||||
RegexpCharacterConstant() { this.isCharacter() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A regexp term that is relevant for this ReDoS analysis.
|
||||
*/
|
||||
class RelevantRegExpTerm extends RegExpTerm {
|
||||
RelevantRegExpTerm() { getRoot(this).isRelevant() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `term` is the chosen canonical representative for all terms with string representation `str`.
|
||||
* The string representation includes which flags are used with the regular expression.
|
||||
*
|
||||
* Using canonical representatives gives a huge performance boost when working with tuples containing multiple `InputSymbol`s.
|
||||
* The number of `InputSymbol`s is decreased by 3 orders of magnitude or more in some larger benchmarks.
|
||||
*/
|
||||
private predicate isCanonicalTerm(RegExpTerm term, string str) {
|
||||
private predicate isCanonicalTerm(RelevantRegExpTerm term, string str) {
|
||||
term =
|
||||
rank[1](RegExpTerm t, Location loc, File file |
|
||||
min(RelevantRegExpTerm t, Location loc, File file |
|
||||
loc = t.getLocation() and
|
||||
file = t.getFile() and
|
||||
str = t.getRawValue()
|
||||
str = t.getRawValue() + "|" + getCanonicalizationFlags(t.getRootTerm())
|
||||
|
|
||||
t order by t.getFile().getRelativePath(), loc.getStartLine(), loc.getStartColumn()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string reperesentation of the flags used with the regular expression.
|
||||
* Only the flags that are relevant for the canonicalization are included.
|
||||
*/
|
||||
string getCanonicalizationFlags(RegExpTerm root) {
|
||||
root.isRootTerm() and
|
||||
(if RegExpFlags::isIgnoreCase(root) then result = "i" else result = "")
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract input symbol, representing a set of concrete characters.
|
||||
*/
|
||||
private newtype TInputSymbol =
|
||||
/** An input symbol corresponding to character `c`. */
|
||||
Char(string c) {
|
||||
c = any(RegexpCharacterConstant cc | getRoot(cc).isRelevant()).getValue().charAt(_)
|
||||
c =
|
||||
any(RegexpCharacterConstant cc |
|
||||
cc instanceof RelevantRegExpTerm and
|
||||
not RegExpFlags::isIgnoreCase(cc.getRootTerm())
|
||||
).getValue().charAt(_)
|
||||
or
|
||||
// normalize everything to lower case if the regexp is case insensitive
|
||||
c =
|
||||
any(RegexpCharacterConstant cc, string char |
|
||||
cc instanceof RelevantRegExpTerm and
|
||||
RegExpFlags::isIgnoreCase(cc.getRootTerm()) and
|
||||
char = cc.getValue().charAt(_)
|
||||
|
|
||||
char.toLowerCase()
|
||||
)
|
||||
} or
|
||||
/**
|
||||
* An input symbol representing all characters matched by
|
||||
* a (non-universal) character class that has string representation `charClassString`.
|
||||
*/
|
||||
CharClass(string charClassString) {
|
||||
exists(RegExpTerm term | term.getRawValue() = charClassString | getRoot(term).isRelevant()) and
|
||||
exists(RegExpTerm recc | isCanonicalTerm(recc, charClassString) |
|
||||
exists(RelevantRegExpTerm recc | isCanonicalTerm(recc, charClassString) |
|
||||
recc instanceof RegExpCharacterClass and
|
||||
not recc.(RegExpCharacterClass).isUniversalClass()
|
||||
or
|
||||
@@ -254,7 +282,7 @@ class InputSymbol extends TInputSymbol {
|
||||
/**
|
||||
* An abstract input symbol that represents a character class.
|
||||
*/
|
||||
abstract private class CharacterClass extends InputSymbol {
|
||||
abstract class CharacterClass extends InputSymbol {
|
||||
/**
|
||||
* Gets a character that is relevant for intersection-tests involving this
|
||||
* character class.
|
||||
@@ -277,7 +305,7 @@ abstract private class CharacterClass extends InputSymbol {
|
||||
/**
|
||||
* Gets a character matched by this character class.
|
||||
*/
|
||||
string choose() { result = this.getARelevantChar() and this.matches(result) }
|
||||
string choose() { result = getARelevantChar() and matches(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,6 +317,19 @@ private module CharacterClasses {
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate hasChildThatMatches(RegExpCharacterClass cc, string char) {
|
||||
if RegExpFlags::isIgnoreCase(cc.getRootTerm())
|
||||
then
|
||||
// normalize everything to lower case if the regexp is case insensitive
|
||||
exists(string c | hasChildThatMatchesIgnoringCasingFlags(cc, c) | char = c.toLowerCase())
|
||||
else hasChildThatMatchesIgnoringCasingFlags(cc, char)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the character class `cc` has a child (constant or range) that matches `char`.
|
||||
* Ignores whether the character class is inside a regular expression that has the ignore case flag.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate hasChildThatMatchesIgnoringCasingFlags(RegExpCharacterClass cc, string char) {
|
||||
exists(getCanonicalCharClass(cc)) and
|
||||
exists(RegExpTerm child | child = cc.getAChild() |
|
||||
char = child.(RegexpCharacterConstant).getValue()
|
||||
@@ -433,7 +474,7 @@ private module CharacterClasses {
|
||||
char = "0123456789".charAt(_)
|
||||
or
|
||||
clazz = "s" and
|
||||
char = [" ", "\t", "\r", "\n", 11.toUnicode(), 12.toUnicode()] // 11.toUnicode() = \v, 12.toUnicode() = \f'
|
||||
char = [" ", "\t", "\r", "\n", 11.toUnicode(), 12.toUnicode()] // 11.toUnicode() = \v, 12.toUnicode() = \f
|
||||
or
|
||||
clazz = "w" and
|
||||
char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".charAt(_)
|
||||
@@ -477,7 +518,7 @@ private module CharacterClasses {
|
||||
result = ["0", "9"]
|
||||
or
|
||||
cc.getValue() = "s" and
|
||||
result = [" "]
|
||||
result = " "
|
||||
or
|
||||
cc.getValue() = "w" and
|
||||
result = ["a", "Z", "_", "0", "9"]
|
||||
@@ -490,7 +531,7 @@ private module CharacterClasses {
|
||||
result = "9"
|
||||
or
|
||||
cc.getValue() = "s" and
|
||||
result = [" "]
|
||||
result = " "
|
||||
or
|
||||
cc.getValue() = "w" and
|
||||
result = "a"
|
||||
@@ -604,7 +645,7 @@ private State before(RegExpTerm t) { result = Match(t, 0) }
|
||||
/**
|
||||
* Gets a state the NFA may be in after matching `t`.
|
||||
*/
|
||||
private State after(RegExpTerm t) {
|
||||
State after(RegExpTerm t) {
|
||||
exists(RegExpAlt alt | t = alt.getAChild() | result = after(alt))
|
||||
or
|
||||
exists(RegExpSequence seq, int i | t = seq.getChild(i) |
|
||||
@@ -633,7 +674,14 @@ private State after(RegExpTerm t) {
|
||||
predicate delta(State q1, EdgeLabel lbl, State q2) {
|
||||
exists(RegexpCharacterConstant s, int i |
|
||||
q1 = Match(s, i) and
|
||||
lbl = Char(s.getValue().charAt(i)) and
|
||||
(
|
||||
not RegExpFlags::isIgnoreCase(s.getRootTerm()) and
|
||||
lbl = Char(s.getValue().charAt(i))
|
||||
or
|
||||
// normalize everything to lower case if the regexp is case insensitive
|
||||
RegExpFlags::isIgnoreCase(s.getRootTerm()) and
|
||||
exists(string c | c = s.getValue().charAt(i) | lbl = Char(c.toLowerCase()))
|
||||
) and
|
||||
(
|
||||
q2 = Match(s, i + 1)
|
||||
or
|
||||
@@ -643,20 +691,20 @@ predicate delta(State q1, EdgeLabel lbl, State q2) {
|
||||
)
|
||||
or
|
||||
exists(RegExpDot dot | q1 = before(dot) and q2 = after(dot) |
|
||||
if dot.getLiteral().isDotAll() then lbl = Any() else lbl = Dot()
|
||||
if RegExpFlags::isDotAll(dot.getRootTerm()) then lbl = Any() else lbl = Dot()
|
||||
)
|
||||
or
|
||||
exists(RegExpCharacterClass cc |
|
||||
cc.isUniversalClass() and q1 = before(cc) and lbl = Any() and q2 = after(cc)
|
||||
or
|
||||
q1 = before(cc) and
|
||||
lbl = CharClass(cc.getRawValue()) and
|
||||
lbl = CharClass(cc.getRawValue() + "|" + getCanonicalizationFlags(cc.getRootTerm())) and
|
||||
q2 = after(cc)
|
||||
)
|
||||
or
|
||||
exists(RegExpCharacterClassEscape cc |
|
||||
q1 = before(cc) and
|
||||
lbl = CharClass(cc.getRawValue()) and
|
||||
lbl = CharClass(cc.getRawValue() + "|" + getCanonicalizationFlags(cc.getRootTerm())) and
|
||||
q2 = after(cc)
|
||||
)
|
||||
or
|
||||
@@ -729,16 +777,27 @@ RegExpRoot getRoot(RegExpTerm term) {
|
||||
result = getRoot(term.getParent())
|
||||
}
|
||||
|
||||
private newtype TState =
|
||||
Match(RegExpTerm t, int i) {
|
||||
getRoot(t).isRelevant() and
|
||||
(
|
||||
i = 0
|
||||
or
|
||||
exists(t.(RegexpCharacterConstant).getValue().charAt(i))
|
||||
)
|
||||
/**
|
||||
* A state in the NFA.
|
||||
*/
|
||||
newtype TState =
|
||||
/**
|
||||
* A state representing that the NFA is about to match a term.
|
||||
* `i` is used to index into multi-char literals.
|
||||
*/
|
||||
Match(RelevantRegExpTerm t, int i) {
|
||||
i = 0
|
||||
or
|
||||
exists(t.(RegexpCharacterConstant).getValue().charAt(i))
|
||||
} or
|
||||
/**
|
||||
* An accept state, where exactly the given input string is accepted.
|
||||
*/
|
||||
Accept(RegExpRoot l) { l.isRelevant() } or
|
||||
/**
|
||||
* An accept state, where the given input string, or any string that has this
|
||||
* string as a prefix, is accepted.
|
||||
*/
|
||||
AcceptAnySuffix(RegExpRoot l) { l.isRelevant() }
|
||||
|
||||
/**
|
||||
@@ -851,29 +910,26 @@ InputSymbol getAnInputSymbolMatching(string char) {
|
||||
result = Any()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `state` is a start state.
|
||||
*/
|
||||
predicate isStartState(State state) {
|
||||
state = mkMatch(any(RegExpRoot r))
|
||||
or
|
||||
exists(RegExpCaret car | state = after(car))
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicates for constructing a prefix string that leads to a given state.
|
||||
*/
|
||||
private module PrefixConstruction {
|
||||
/**
|
||||
* Holds if `state` starts the string matched by the regular expression.
|
||||
*/
|
||||
private predicate isStartState(State state) {
|
||||
state instanceof StateInPumpableRegexp and
|
||||
(
|
||||
state = Match(any(RegExpRoot r), _)
|
||||
or
|
||||
exists(RegExpCaret car | state = after(car))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `state` is the textually last start state for the regular expression.
|
||||
*/
|
||||
private predicate lastStartState(State state) {
|
||||
exists(RegExpRoot root |
|
||||
state =
|
||||
max(State s, Location l |
|
||||
max(StateInPumpableRegexp s, Location l |
|
||||
isStartState(s) and getRoot(s.getRepr()) = root and l = s.getRepr().getLocation()
|
||||
|
|
||||
s
|
||||
@@ -1173,7 +1229,6 @@ private predicate isReDoSAttackable(RegExpTerm term, string pump, State s) {
|
||||
* `prefixMsg` contains a friendly message for a prefix that reaches `s` (or `prefixMsg` is the empty string if the prefix is empty or if no prefix could be found).
|
||||
*/
|
||||
predicate hasReDoSResult(RegExpTerm t, string pump, State s, string prefixMsg) {
|
||||
not t.getRegExp().hasFreeSpacingFlag() and // exclude free-spacing mode regexes
|
||||
isReDoSAttackable(t, pump, s) and
|
||||
(
|
||||
prefixMsg = "starting with '" + escape(PrefixConstruction::prefix(s)) + "' and " and
|
||||
@@ -2,6 +2,42 @@ private import codeql.ruby.ast.Literal as AST
|
||||
private import codeql.Locations
|
||||
private import ParseRegExp
|
||||
|
||||
/**
|
||||
* Holds if the regular expression should not be considered.
|
||||
*/
|
||||
predicate isExcluded(RegExpParent parent) {
|
||||
parent.(RegExpTerm).getRegExp().hasFreeSpacingFlag() // exclude free-spacing mode regexes
|
||||
}
|
||||
|
||||
/**
|
||||
* A module containing predicates for determining which flags a regular expression have.
|
||||
*/
|
||||
module RegExpFlags {
|
||||
/**
|
||||
* Holds if `root` has the `i` flag for case-insensitive matching.
|
||||
*/
|
||||
predicate isIgnoreCase(RegExpTerm root) {
|
||||
root.isRootTerm() and
|
||||
root.getLiteral().isIgnoreCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flags for `root`, or the empty string if `root` has no flags.
|
||||
*/
|
||||
string getFlags(RegExpTerm root) {
|
||||
root.isRootTerm() and
|
||||
result = root.getLiteral().getFlags()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `root` has the `s` flag for multi-line matching.
|
||||
*/
|
||||
predicate isDotAll(RegExpTerm root) {
|
||||
root.isRootTerm() and
|
||||
root.getLiteral().isDotAll()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An element containing a regular expression term, that is, either
|
||||
* a string literal (parsed as a regular expression)
|
||||
@@ -38,6 +74,10 @@ class RegExpLiteral extends TRegExpLiteral, RegExpParent {
|
||||
|
||||
predicate isDotAll() { re.hasMultilineFlag() }
|
||||
|
||||
predicate isIgnoreCase() { re.hasCaseInsensitiveFlag() }
|
||||
|
||||
string getFlags() { result = re.getFlagString() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpLiteral" }
|
||||
}
|
||||
|
||||
@@ -398,6 +438,13 @@ private int toHex(string hex) {
|
||||
result = 15 and hex = ["f", "F"]
|
||||
}
|
||||
|
||||
/**
|
||||
* A word boundary, that is, a regular expression term of the form `\b`.
|
||||
*/
|
||||
class RegExpWordBoundary extends RegExpEscape {
|
||||
RegExpWordBoundary() { this.getUnescaped() = "b" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A character class escape in a regular expression.
|
||||
* That is, an escaped character that denotes multiple characters.
|
||||
@@ -99,11 +99,11 @@ predicate returnStep(Node nodeFrom, Node nodeTo) {
|
||||
* to `z` inside `bar`, even though this content write happens _after_ `bar` is
|
||||
* called.
|
||||
*/
|
||||
predicate basicStoreStep(Node nodeFrom, DataFlowPublic::LocalSourceNode nodeTo, string content) {
|
||||
predicate basicStoreStep(Node nodeFrom, Node nodeTo, string content) {
|
||||
// TODO: support SetterMethodCall inside TuplePattern
|
||||
exists(ExprNodes::MethodCallCfgNode call |
|
||||
content = getSetterCallAttributeName(call.getExpr()) and
|
||||
nodeTo.(DataFlowPublic::ExprNode).getExprNode() = call.getReceiver() and
|
||||
nodeTo.(DataFlowPrivate::PostUpdateNode).getPreUpdateNode().asExpr() = call.getReceiver() and
|
||||
call.getExpr() instanceof AST::SetterMethodCall and
|
||||
call.getArgument(call.getNumberOfArguments() - 1) =
|
||||
nodeFrom.(DataFlowPublic::ExprNode).getExprNode()
|
||||
|
||||
Reference in New Issue
Block a user