mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
Merge branch 'main' of https://github.com/github/codeql into python-ruby/track-through-summaries-pm
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
## 0.6.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Support for the `sqlite3` gem has been added. Method calls that execute queries against an SQLite3 database that may be vulnerable to injection attacks will now be recognized.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
4
ruby/ql/lib/change-notes/2023-05-06-mysql2.md
Normal file
4
ruby/ql/lib/change-notes/2023-05-06-mysql2.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Support for the `mysql2` gem has been added. Method calls that execute queries against an MySQL database that may be vulnerable to injection attacks will now be recognized.
|
||||
4
ruby/ql/lib/change-notes/2023-05-07-sequel.md
Normal file
4
ruby/ql/lib/change-notes/2023-05-07-sequel.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Support for the `sequel` gem has been added. Method calls that execute queries against a database that may be vulnerable to injection attacks will now be recognized.
|
||||
7
ruby/ql/lib/change-notes/2023-06-02-delete-deps.md
Normal file
7
ruby/ql/lib/change-notes/2023-06-02-delete-deps.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Deleted many deprecated predicates and classes with uppercase `URL`, `XSS`, etc. in their names. Use the PascalCased versions instead.
|
||||
* Deleted the deprecated `getValueText` predicate from the `Expr`, `StringComponent`, and `ExprCfgNode` classes. Use `getConstantValue` instead.
|
||||
* Deleted the deprecated `VariableReferencePattern` class, use `ReferencePattern` instead.
|
||||
* Deleted all deprecated aliases in `StandardLibrary.qll`, use `codeql.ruby.frameworks.Core` and `codeql.ruby.frameworks.Stdlib` instead.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
## 0.6.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Support for the `sqlite3` gem has been added. Method calls that execute queries against an SQLite3 database that may be vulnerable to injection attacks will now be recognized.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.1
|
||||
lastReleaseVersion: 0.6.2
|
||||
|
||||
@@ -778,7 +778,7 @@ module API {
|
||||
or
|
||||
exists(TypeTracker t2 |
|
||||
result = trackUseNode(src, t2).track(t2, t) and
|
||||
not result instanceof DataFlowPrivate::SelfParameterNode
|
||||
not result instanceof DataFlow::SelfParameterNode
|
||||
)
|
||||
}
|
||||
|
||||
@@ -800,7 +800,7 @@ module API {
|
||||
or
|
||||
exists(TypeBackTracker t2, DataFlow::LocalSourceNode mid |
|
||||
mid = trackDefNode(rhs, t2) and
|
||||
not mid instanceof DataFlowPrivate::SelfParameterNode and
|
||||
not mid instanceof DataFlow::SelfParameterNode and
|
||||
result = mid.backtrack(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -78,6 +78,19 @@ module SqlExecution {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that performs SQL sanitization.
|
||||
*/
|
||||
class SqlSanitization extends DataFlow::Node instanceof SqlSanitization::Range { }
|
||||
|
||||
/** Provides a class for modeling new SQL sanitization APIs. */
|
||||
module SqlSanitization {
|
||||
/**
|
||||
* A data-flow node that performs SQL sanitization.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node { }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that executes a regular expression.
|
||||
*
|
||||
@@ -687,9 +700,7 @@ module Http {
|
||||
* 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.
|
||||
*/
|
||||
deprecated DataFlow::Node getURL() {
|
||||
result = super.getURL() or result = Request::Range.super.getAUrlPart()
|
||||
}
|
||||
deprecated DataFlow::Node getURL() { result = Request::Range.super.getAUrlPart() }
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
@@ -715,14 +726,6 @@ module Http {
|
||||
/** Gets a node which returns the body of the response */
|
||||
abstract DataFlow::Node getResponseBody();
|
||||
|
||||
/**
|
||||
* DEPRECATED: overwrite `getAUrlPart` instead.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
deprecated DataFlow::Node getURL() { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: override `disablesCertificateValidation/2` instead.
|
||||
*
|
||||
|
||||
@@ -32,4 +32,6 @@ private import codeql.ruby.frameworks.Slim
|
||||
private import codeql.ruby.frameworks.Sinatra
|
||||
private import codeql.ruby.frameworks.Twirp
|
||||
private import codeql.ruby.frameworks.Sqlite3
|
||||
private import codeql.ruby.frameworks.Mysql2
|
||||
private import codeql.ruby.frameworks.Pg
|
||||
private import codeql.ruby.frameworks.Sequel
|
||||
|
||||
@@ -11,13 +11,6 @@ private import internal.TreeSitter
|
||||
* This is the root QL class for all expressions.
|
||||
*/
|
||||
class Expr extends Stmt, TExpr {
|
||||
/**
|
||||
* DEPRECATED: Use `getConstantValue` instead.
|
||||
*
|
||||
* Gets the textual (constant) value of this expression, if any.
|
||||
*/
|
||||
deprecated string getValueText() { result = this.getConstantValue().toString() }
|
||||
|
||||
/** Gets the constant value of this expression, if any. */
|
||||
ConstantValue getConstantValue() { result = getConstantValueExpr(this) }
|
||||
}
|
||||
|
||||
@@ -165,14 +165,6 @@ class FileLiteral extends Literal instanceof FileLiteralImpl {
|
||||
* `StringEscapeSequenceComponent`, or `StringInterpolationComponent`.
|
||||
*/
|
||||
class StringComponent extends AstNode instanceof StringComponentImpl {
|
||||
/**
|
||||
* DEPRECATED: Use `getConstantValue` instead.
|
||||
*
|
||||
* Gets the source text for this string component. Has no result if this is
|
||||
* a `StringInterpolationComponent`.
|
||||
*/
|
||||
deprecated string getValueText() { result = this.getConstantValue().toString() }
|
||||
|
||||
/** Gets the constant value of this string component, if any. */
|
||||
ConstantValue::ConstantStringValue getConstantValue() { result = TString(super.getValue()) }
|
||||
}
|
||||
@@ -218,8 +210,6 @@ class StringInterpolationComponent extends StringComponent, StmtSequence instanc
|
||||
|
||||
final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
deprecated final override string getValueText() { none() }
|
||||
|
||||
final override ConstantValue::ConstantStringValue getConstantValue() {
|
||||
result = StmtSequence.super.getConstantValue()
|
||||
}
|
||||
@@ -267,8 +257,6 @@ class RegExpInterpolationComponent extends RegExpComponent, StmtSequence instanc
|
||||
|
||||
final override Stmt getStmt(int n) { toGenerated(result) = g.getChild(n) }
|
||||
|
||||
deprecated final override string getValueText() { none() }
|
||||
|
||||
final override ConstantValue::ConstantStringValue getConstantValue() {
|
||||
result = StmtSequence.super.getConstantValue()
|
||||
}
|
||||
|
||||
@@ -363,19 +363,3 @@ class ReferencePattern extends CasePattern, TReferencePattern {
|
||||
pred = "getExpr" and result = this.getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `ReferencePattern` instead.
|
||||
*
|
||||
* A variable reference in a pattern, i.e. `^x` in the following example:
|
||||
* ```rb
|
||||
* x = 10
|
||||
* case expr
|
||||
* in ^x then puts "ok"
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
deprecated class VariableReferencePattern extends ReferencePattern, TVariableReferencePattern {
|
||||
/** Gets the variable access corresponding to this variable reference pattern. */
|
||||
final VariableReadAccess getVariableAccess() { result = this.getExpr() }
|
||||
}
|
||||
|
||||
@@ -113,13 +113,6 @@ class ExprCfgNode extends AstCfgNode {
|
||||
/** Gets the underlying expression. */
|
||||
Expr getExpr() { result = e }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getConstantValue` instead.
|
||||
*
|
||||
* Gets the textual (constant) value of this expression, if any.
|
||||
*/
|
||||
deprecated string getValueText() { result = this.getConstantValue().toString() }
|
||||
|
||||
/** Gets the constant value of this expression, if any. */
|
||||
ConstantValue getConstantValue() { result = getConstantValue(this) }
|
||||
}
|
||||
|
||||
@@ -8,18 +8,21 @@ private import FlowSummaryImpl as FlowSummaryImpl
|
||||
private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.dataflow.SSA
|
||||
private import codeql.util.Boolean
|
||||
private import codeql.util.Unit
|
||||
|
||||
/**
|
||||
* A `LocalSourceNode` for a `self` variable. This is either an implicit `self`
|
||||
* parameter or an implicit SSA entry definition.
|
||||
* A `LocalSourceNode` for a `self` variable. This is the implicit `self`
|
||||
* parameter, when it exists, otherwise the implicit SSA entry definition.
|
||||
*/
|
||||
private class SelfLocalSourceNode extends DataFlow::LocalSourceNode {
|
||||
private SelfVariable self;
|
||||
|
||||
SelfLocalSourceNode() {
|
||||
self = this.(SelfParameterNode).getSelfVariable()
|
||||
self = this.(SelfParameterNodeImpl).getSelfVariable()
|
||||
or
|
||||
self = this.(SsaSelfDefinitionNode).getVariable()
|
||||
self = this.(SsaSelfDefinitionNode).getVariable() and
|
||||
not LocalFlow::localFlowSsaParamInput(_, this)
|
||||
}
|
||||
|
||||
/** Gets the `self` variable. */
|
||||
@@ -470,35 +473,28 @@ private module Cached {
|
||||
import Cached
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackModuleAccess(Module m, TypeTracker t) {
|
||||
t.start() and m = resolveConstantReadAccess(result.asExpr().getExpr())
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackModuleAccessRec(m, t2, summary) and t = t2.append(summary)
|
||||
)
|
||||
private predicate isNotSelf(DataFlow::Node n) { not n instanceof SelfParameterNodeImpl }
|
||||
|
||||
private module TrackModuleInput implements CallGraphConstruction::Simple::InputSig {
|
||||
class State = Module;
|
||||
|
||||
predicate start(DataFlow::Node start, Module m) {
|
||||
m = resolveConstantReadAccess(start.asExpr().getExpr())
|
||||
}
|
||||
|
||||
// We exclude steps into `self` parameters, and instead rely on the type of the
|
||||
// enclosing module
|
||||
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
|
||||
}
|
||||
|
||||
/**
|
||||
* We exclude steps into `self` parameters, and instead rely on the type of the
|
||||
* enclosing module.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackModuleAccessRec(Module m, TypeTracker t, StepSummary summary) {
|
||||
StepSummary::step(trackModuleAccess(m, t), result, summary) and
|
||||
not result instanceof SelfParameterNode
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackModuleAccess(Module m) {
|
||||
result = trackModuleAccess(m, TypeTracker::end())
|
||||
}
|
||||
predicate trackModuleAccess = CallGraphConstruction::Simple::Make<TrackModuleInput>::track/1;
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasUserDefinedNew(Module m) {
|
||||
exists(DataFlow::MethodNode method |
|
||||
// not `getAnAncestor` because singleton methods cannot be included
|
||||
singletonMethodOnModule(method.asCallableAstNode(), "new", m.getSuperClass*()) and
|
||||
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturningNode())
|
||||
not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturnNode())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -531,141 +527,162 @@ private predicate isStandardNewCall(RelevantCall new, Module m, boolean exact) {
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `n` is an instance of type `tp`. */
|
||||
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
|
||||
n.asExpr().getExpr() instanceof NilLiteral and
|
||||
tp = TResolved("NilClass") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
|
||||
tp = TResolved("FalseClass") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
|
||||
tp = TResolved("TrueClass") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof IntegerLiteral and
|
||||
tp = TResolved("Integer") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof FloatLiteral and
|
||||
tp = TResolved("Float") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof RationalLiteral and
|
||||
tp = TResolved("Rational") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof ComplexLiteral and
|
||||
tp = TResolved("Complex") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof StringlikeLiteral and
|
||||
tp = TResolved("String") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
|
||||
tp = TResolved("Array") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
|
||||
tp = TResolved("Hash") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof MethodBase and
|
||||
tp = TResolved("Symbol") and
|
||||
exact = true
|
||||
or
|
||||
n.asParameter() instanceof BlockParameter and
|
||||
tp = TResolved("Proc") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof Lambda and
|
||||
tp = TResolved("Proc") and
|
||||
exact = true
|
||||
or
|
||||
isStandardNewCall(n.asExpr(), tp, exact)
|
||||
or
|
||||
// `self` reference in method or top-level (but not in module or singleton method,
|
||||
// where instance methods cannot be called; only singleton methods)
|
||||
n =
|
||||
any(SelfLocalSourceNode self |
|
||||
exists(MethodBase m |
|
||||
selfInMethod(self.getVariable(), m, tp) and
|
||||
not m instanceof SingletonMethod and
|
||||
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
|
||||
)
|
||||
or
|
||||
selfInToplevel(self.getVariable(), tp) and
|
||||
exact = true
|
||||
)
|
||||
or
|
||||
// `in C => c then c.foo`
|
||||
asModulePattern(n, tp) and
|
||||
exact = false
|
||||
or
|
||||
// `case object when C then object.foo`
|
||||
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
|
||||
exact = false
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackInstance(Module tp, boolean exact, TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
isInstance(result, tp, exact)
|
||||
or
|
||||
exists(Module m |
|
||||
(if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and
|
||||
exact = true
|
||||
|
|
||||
// needed for e.g. `C.new`
|
||||
m = resolveConstantReadAccess(result.asExpr().getExpr())
|
||||
or
|
||||
// needed for e.g. `self.include`
|
||||
selfInModule(result.(SelfLocalSourceNode).getVariable(), m)
|
||||
or
|
||||
// needed for e.g. `self.puts`
|
||||
selfInMethod(result.(SelfLocalSourceNode).getVariable(), any(SingletonMethod sm), m)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackInstanceRec(tp, t2, exact, summary) and t = t2.append(summary)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate localFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
localFlowStepTypeTracker(nodeFrom, nodeTo) and
|
||||
summary.toString() = "level"
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) {
|
||||
hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _)
|
||||
}
|
||||
|
||||
/**
|
||||
* We exclude steps into `self` parameters and type checked variables. For those,
|
||||
* we instead rely on the type of the enclosing module resp. the type being checked
|
||||
* against, and apply an open-world assumption when determining possible dispatch
|
||||
* targets.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackInstanceRec(Module tp, TypeTracker t, boolean exact, StepSummary summary) {
|
||||
exists(DataFlow::Node mid | mid = trackInstance(tp, exact, t) |
|
||||
StepSummary::smallstep(mid, result, summary) and
|
||||
not result instanceof SelfParameterNode
|
||||
private module TrackInstanceInput implements CallGraphConstruction::InputSig {
|
||||
pragma[nomagic]
|
||||
private predicate isInstanceNoCall(DataFlow::Node n, Module tp, boolean exact) {
|
||||
n.asExpr().getExpr() instanceof NilLiteral and
|
||||
tp = TResolved("NilClass") and
|
||||
exact = true
|
||||
or
|
||||
localFlowStep(mid, result, summary) and
|
||||
not hasAdjacentTypeCheckedReads(result)
|
||||
)
|
||||
n.asExpr().getExpr().(BooleanLiteral).isFalse() and
|
||||
tp = TResolved("FalseClass") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr().(BooleanLiteral).isTrue() and
|
||||
tp = TResolved("TrueClass") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof IntegerLiteral and
|
||||
tp = TResolved("Integer") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof FloatLiteral and
|
||||
tp = TResolved("Float") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof RationalLiteral and
|
||||
tp = TResolved("Rational") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof ComplexLiteral and
|
||||
tp = TResolved("Complex") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof StringlikeLiteral and
|
||||
tp = TResolved("String") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr() instanceof CfgNodes::ExprNodes::ArrayLiteralCfgNode and
|
||||
tp = TResolved("Array") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr() instanceof CfgNodes::ExprNodes::HashLiteralCfgNode and
|
||||
tp = TResolved("Hash") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof MethodBase and
|
||||
tp = TResolved("Symbol") and
|
||||
exact = true
|
||||
or
|
||||
n.asParameter() instanceof BlockParameter and
|
||||
tp = TResolved("Proc") and
|
||||
exact = true
|
||||
or
|
||||
n.asExpr().getExpr() instanceof Lambda and
|
||||
tp = TResolved("Proc") and
|
||||
exact = true
|
||||
or
|
||||
// `self` reference in method or top-level (but not in module or singleton method,
|
||||
// where instance methods cannot be called; only singleton methods)
|
||||
n =
|
||||
any(SelfLocalSourceNode self |
|
||||
exists(MethodBase m |
|
||||
selfInMethod(self.getVariable(), m, tp) and
|
||||
not m instanceof SingletonMethod and
|
||||
if m.getEnclosingModule() instanceof Toplevel then exact = true else exact = false
|
||||
)
|
||||
or
|
||||
selfInToplevel(self.getVariable(), tp) and
|
||||
exact = true
|
||||
)
|
||||
or
|
||||
// `in C => c then c.foo`
|
||||
asModulePattern(n, tp) and
|
||||
exact = false
|
||||
or
|
||||
// `case object when C then object.foo`
|
||||
hasAdjacentTypeCheckedReads(_, _, n.asExpr(), tp) and
|
||||
exact = false
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate isInstanceCall(DataFlow::Node n, Module tp, boolean exact) {
|
||||
isStandardNewCall(n.asExpr(), tp, exact)
|
||||
}
|
||||
|
||||
/** Holds if `n` is an instance of type `tp`. */
|
||||
pragma[inline]
|
||||
private predicate isInstance(DataFlow::Node n, Module tp, boolean exact) {
|
||||
isInstanceNoCall(n, tp, exact)
|
||||
or
|
||||
isInstanceCall(n, tp, exact)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasAdjacentTypeCheckedReads(DataFlow::Node node) {
|
||||
hasAdjacentTypeCheckedReads(_, _, node.asExpr(), _)
|
||||
}
|
||||
|
||||
newtype State = additional MkState(Module m, Boolean exact)
|
||||
|
||||
predicate start(DataFlow::Node start, State state) {
|
||||
exists(Module tp, boolean exact | state = MkState(tp, exact) |
|
||||
isInstance(start, tp, exact)
|
||||
or
|
||||
exists(Module m |
|
||||
(if m.isClass() then tp = TResolved("Class") else tp = TResolved("Module")) and
|
||||
exact = true
|
||||
|
|
||||
// needed for e.g. `C.new`
|
||||
m = resolveConstantReadAccess(start.asExpr().getExpr())
|
||||
or
|
||||
// needed for e.g. `self.include`
|
||||
selfInModule(start.(SelfLocalSourceNode).getVariable(), m)
|
||||
or
|
||||
// needed for e.g. `self.puts`
|
||||
selfInMethod(start.(SelfLocalSourceNode).getVariable(), any(SingletonMethod sm), m)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
// We exclude steps into `self` parameters. For those, we instead rely on the type of
|
||||
// the enclosing module
|
||||
StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary) and
|
||||
isNotSelf(nodeTo)
|
||||
or
|
||||
// We exclude steps into type checked variables. For those, we instead rely on the
|
||||
// type being checked against
|
||||
localFlowStep(nodeFrom, nodeTo, summary) and
|
||||
not hasAdjacentTypeCheckedReads(nodeTo)
|
||||
}
|
||||
|
||||
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
StepSummary::smallstepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
class StateProj = Unit;
|
||||
|
||||
Unit stateProj(State state) { exists(state) and exists(result) }
|
||||
|
||||
// We exclude steps into `self` parameters, and instead rely on the type of the
|
||||
// enclosing module
|
||||
predicate filter(DataFlow::Node n, Unit u) {
|
||||
n instanceof SelfParameterNodeImpl and
|
||||
exists(u)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackInstance(Module tp, boolean exact) {
|
||||
result = trackInstance(tp, exact, TypeTracker::end())
|
||||
result =
|
||||
CallGraphConstruction::Make<TrackInstanceInput>::track(TrackInstanceInput::MkState(tp, exact))
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
@@ -706,30 +723,17 @@ private CfgScope getTargetInstance(RelevantCall call, string method) {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackBlock(Block block, TypeTracker t) {
|
||||
t.start() and result.asExpr().getExpr() = block
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackBlockRec(block, t2, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
private module TrackBlockInput implements CallGraphConstruction::Simple::InputSig {
|
||||
class State = Block;
|
||||
|
||||
predicate start(DataFlow::Node start, Block block) { start.asExpr().getExpr() = block }
|
||||
|
||||
// We exclude steps into `self` parameters, and instead rely on the type of the
|
||||
// enclosing module
|
||||
predicate filter(DataFlow::Node n) { n instanceof SelfParameterNodeImpl }
|
||||
}
|
||||
|
||||
/**
|
||||
* We exclude steps into `self` parameters, which may happen when the code
|
||||
* base contains implementations of `call`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackBlockRec(Block block, TypeTracker t, StepSummary summary) {
|
||||
StepSummary::step(trackBlock(block, t), result, summary) and
|
||||
not result instanceof SelfParameterNode
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode trackBlock(Block block) {
|
||||
result = trackBlock(block, TypeTracker::end())
|
||||
}
|
||||
private predicate trackBlock = CallGraphConstruction::Simple::Make<TrackBlockInput>::track/1;
|
||||
|
||||
/** Holds if `m` is a singleton method named `name`, defined on `object. */
|
||||
private predicate singletonMethod(MethodBase m, string name, Expr object) {
|
||||
@@ -896,92 +900,98 @@ predicate singletonMethodOnInstance(MethodBase method, string name, Expr object)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
|
||||
*
|
||||
* This is only used for tracking singleton methods, where we want to be able
|
||||
* to handle cases like
|
||||
*
|
||||
* ```rb
|
||||
* def add_singleton x
|
||||
* def x.foo; end
|
||||
* end
|
||||
*
|
||||
* y = add_singleton C.new
|
||||
* y.foo
|
||||
* ```
|
||||
*
|
||||
* and
|
||||
*
|
||||
* ```rb
|
||||
* class C
|
||||
* def add_singleton_to_self
|
||||
* def self.foo; end
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* y = C.new
|
||||
* y.add_singleton_to_self
|
||||
* y.foo
|
||||
* ```
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate paramReturnFlow(
|
||||
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
|
||||
) {
|
||||
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
|
||||
TypeTrackerSpecific::callStep(call, arg, p) and
|
||||
nodeTo.getPreUpdateNode() = arg and
|
||||
summary.toString() = "return" and
|
||||
(
|
||||
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
|
||||
private module TrackSingletonMethodOnInstanceInput implements CallGraphConstruction::InputSig {
|
||||
/**
|
||||
* Holds if there is reverse flow from `nodeFrom` to `nodeTo` via a parameter.
|
||||
*
|
||||
* This is only used for tracking singleton methods, where we want to be able
|
||||
* to handle cases like
|
||||
*
|
||||
* ```rb
|
||||
* def add_singleton x
|
||||
* def x.foo; end
|
||||
* end
|
||||
*
|
||||
* y = add_singleton C.new
|
||||
* y.foo
|
||||
* ```
|
||||
*
|
||||
* and
|
||||
*
|
||||
* ```rb
|
||||
* class C
|
||||
* def add_singleton_to_self
|
||||
* def self.foo; end
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* y = C.new
|
||||
* y.add_singleton_to_self
|
||||
* y.foo
|
||||
* ```
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate paramReturnFlow(
|
||||
DataFlow::Node nodeFrom, DataFlow::PostUpdateNode nodeTo, StepSummary summary
|
||||
) {
|
||||
exists(RelevantCall call, DataFlow::Node arg, DataFlow::ParameterNode p, Expr nodeFromPreExpr |
|
||||
TypeTrackerSpecific::callStep(call, arg, p) and
|
||||
nodeTo.getPreUpdateNode() = arg and
|
||||
summary.toString() = "return" and
|
||||
(
|
||||
nodeFromPreExpr = nodeFrom.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr().getExpr()
|
||||
or
|
||||
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
|
||||
singletonMethodOnInstance(_, _, nodeFromPreExpr)
|
||||
)
|
||||
|
|
||||
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
|
||||
or
|
||||
nodeFromPreExpr = nodeFrom.asExpr().getExpr() and
|
||||
singletonMethodOnInstance(_, _, nodeFromPreExpr)
|
||||
nodeFromPreExpr = p.(SelfParameterNodeImpl).getSelfVariable().getAnAccess()
|
||||
)
|
||||
|
|
||||
nodeFromPreExpr = p.getParameter().(NamedParameter).getVariable().getAnAccess()
|
||||
or
|
||||
nodeFromPreExpr = p.(SelfParameterNode).getSelfVariable().getAnAccess()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name, TypeTracker t) {
|
||||
t.start() and
|
||||
singletonMethodOnInstance(method, name, result.asExpr().getExpr())
|
||||
or
|
||||
exists(TypeTracker t2, StepSummary summary |
|
||||
result = trackSingletonMethodOnInstanceRec(method, name, t2, summary) and
|
||||
t = t2.append(summary) and
|
||||
// Stop flow at redefinitions.
|
||||
//
|
||||
// Example:
|
||||
// ```rb
|
||||
// def x.foo; end
|
||||
// def x.foo; end
|
||||
// x.foo # <- we want to resolve this call to the second definition only
|
||||
// ```
|
||||
not singletonMethodOnInstance(_, name, result.asExpr().getExpr())
|
||||
)
|
||||
}
|
||||
class State = MethodBase;
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackSingletonMethodOnInstanceRec(
|
||||
MethodBase method, string name, TypeTracker t, StepSummary summary
|
||||
) {
|
||||
exists(DataFlow::Node mid | mid = trackSingletonMethodOnInstance(method, name, t) |
|
||||
StepSummary::smallstep(mid, result, summary)
|
||||
predicate start(DataFlow::Node start, MethodBase method) {
|
||||
singletonMethodOnInstance(method, _, start.asExpr().getExpr())
|
||||
}
|
||||
|
||||
predicate stepNoCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
StepSummary::smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
paramReturnFlow(mid, result, summary)
|
||||
localFlowStep(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
predicate stepCall(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, StepSummary summary) {
|
||||
StepSummary::smallstepCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
localFlowStep(mid, result, summary)
|
||||
)
|
||||
paramReturnFlow(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
class StateProj extends string {
|
||||
StateProj() { singletonMethodOnInstance(_, this, _) }
|
||||
}
|
||||
|
||||
StateProj stateProj(MethodBase method) { singletonMethodOnInstance(method, result, _) }
|
||||
|
||||
// Stop flow at redefinitions.
|
||||
//
|
||||
// Example:
|
||||
// ```rb
|
||||
// def x.foo; end
|
||||
// def x.foo; end
|
||||
// x.foo # <- we want to resolve this call to the second definition only
|
||||
// ```
|
||||
predicate filter(DataFlow::Node n, StateProj name) {
|
||||
singletonMethodOnInstance(_, name, n.asExpr().getExpr())
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::Node trackSingletonMethodOnInstance(MethodBase method, string name) {
|
||||
result = trackSingletonMethodOnInstance(method, name, TypeTracker::end())
|
||||
result = CallGraphConstruction::Make<TrackSingletonMethodOnInstanceInput>::track(method) and
|
||||
singletonMethodOnInstance(method, name, _)
|
||||
}
|
||||
|
||||
/** Holds if a `self` access may be the receiver of `call` directly inside module `m`. */
|
||||
|
||||
@@ -1135,8 +1135,8 @@ module Impl<FullStateConfigSig Config> {
|
||||
DataFlowCall call, ArgNodeEx arg, ParamNodeEx p, boolean allowsFieldFlow
|
||||
);
|
||||
|
||||
bindingset[node, state, t, ap]
|
||||
predicate filter(NodeEx node, FlowState state, Typ t, Ap ap);
|
||||
bindingset[node, state, t0, ap]
|
||||
predicate filter(NodeEx node, FlowState state, Typ t0, Ap ap, Typ t);
|
||||
|
||||
bindingset[typ, contentType]
|
||||
predicate typecheckStore(Typ typ, DataFlowType contentType);
|
||||
@@ -1199,17 +1199,21 @@ module Impl<FullStateConfigSig Config> {
|
||||
NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, TypOption argT,
|
||||
ApOption argAp, Typ t, Ap ap, ApApprox apa
|
||||
) {
|
||||
fwdFlow0(node, state, cc, summaryCtx, argT, argAp, t, ap, apa) and
|
||||
PrevStage::revFlow(node, state, apa) and
|
||||
filter(node, state, t, ap)
|
||||
fwdFlow1(node, state, cc, summaryCtx, argT, argAp, _, t, ap, apa)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
additional predicate fwdFlow(
|
||||
private predicate fwdFlow1(
|
||||
NodeEx node, FlowState state, Cc cc, ParamNodeOption summaryCtx, TypOption argT,
|
||||
ApOption argAp, Typ t, Ap ap
|
||||
ApOption argAp, Typ t0, Typ t, Ap ap, ApApprox apa
|
||||
) {
|
||||
fwdFlow(node, state, cc, summaryCtx, argT, argAp, t, ap, _)
|
||||
fwdFlow0(node, state, cc, summaryCtx, argT, argAp, t0, ap, apa) and
|
||||
PrevStage::revFlow(node, state, apa) and
|
||||
filter(node, state, t0, ap, t)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate typeStrengthen(Typ t0, Ap ap, Typ t) {
|
||||
fwdFlow1(_, _, _, _, _, _, t0, t, ap, _) and t0 != t
|
||||
}
|
||||
|
||||
pragma[assume_small_delta]
|
||||
@@ -1339,6 +1343,11 @@ module Impl<FullStateConfigSig Config> {
|
||||
private predicate fwdFlowConsCand(Typ t2, Ap cons, Content c, Typ t1, Ap tail) {
|
||||
fwdFlowStore(_, t1, tail, c, t2, _, _, _, _, _, _) and
|
||||
cons = apCons(c, t1, tail)
|
||||
or
|
||||
exists(Typ t0 |
|
||||
typeStrengthen(t0, cons, t2) and
|
||||
fwdFlowConsCand(t0, cons, c, t1, tail)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
@@ -1359,7 +1368,7 @@ module Impl<FullStateConfigSig Config> {
|
||||
ParamNodeOption summaryCtx, TypOption argT, ApOption argAp
|
||||
) {
|
||||
exists(ApHeadContent apc |
|
||||
fwdFlow(node1, state, cc, summaryCtx, argT, argAp, t, ap) and
|
||||
fwdFlow(node1, state, cc, summaryCtx, argT, argAp, t, ap, _) and
|
||||
apc = getHeadContent(ap) and
|
||||
readStepCand0(node1, apc, c, node2)
|
||||
)
|
||||
@@ -1520,14 +1529,14 @@ module Impl<FullStateConfigSig Config> {
|
||||
NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap
|
||||
) {
|
||||
revFlow0(node, state, returnCtx, returnAp, ap) and
|
||||
fwdFlow(node, state, _, _, _, _, _, ap)
|
||||
fwdFlow(node, state, _, _, _, _, _, ap, _)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlow0(
|
||||
NodeEx node, FlowState state, ReturnCtx returnCtx, ApOption returnAp, Ap ap
|
||||
) {
|
||||
fwdFlow(node, state, _, _, _, _, _, ap) and
|
||||
fwdFlow(node, state, _, _, _, _, _, ap, _) and
|
||||
sinkNode(node, state) and
|
||||
(
|
||||
if hasSinkCallCtx()
|
||||
@@ -1780,13 +1789,13 @@ module Impl<FullStateConfigSig Config> {
|
||||
boolean fwd, int nodes, int fields, int conscand, int states, int tuples
|
||||
) {
|
||||
fwd = true and
|
||||
nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, _, _)) and
|
||||
nodes = count(NodeEx node | fwdFlow(node, _, _, _, _, _, _, _, _)) and
|
||||
fields = count(Content f0 | fwdConsCand(f0, _, _)) and
|
||||
conscand = count(Content f0, Typ t, Ap ap | fwdConsCand(f0, t, ap)) and
|
||||
states = count(FlowState state | fwdFlow(_, state, _, _, _, _, _, _)) and
|
||||
states = count(FlowState state | fwdFlow(_, state, _, _, _, _, _, _, _)) and
|
||||
tuples =
|
||||
count(NodeEx n, FlowState state, Cc cc, ParamNodeOption summaryCtx, TypOption argT,
|
||||
ApOption argAp, Typ t, Ap ap | fwdFlow(n, state, cc, summaryCtx, argT, argAp, t, ap))
|
||||
ApOption argAp, Typ t, Ap ap | fwdFlow(n, state, cc, summaryCtx, argT, argAp, t, ap, _))
|
||||
or
|
||||
fwd = false and
|
||||
nodes = count(NodeEx node | revFlow(node, _, _, _, _)) and
|
||||
@@ -1963,10 +1972,10 @@ module Impl<FullStateConfigSig Config> {
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[node, state, t, ap]
|
||||
predicate filter(NodeEx node, FlowState state, Typ t, Ap ap) {
|
||||
bindingset[node, state, t0, ap]
|
||||
predicate filter(NodeEx node, FlowState state, Typ t0, Ap ap, Typ t) {
|
||||
PrevStage::revFlowState(state) and
|
||||
exists(t) and
|
||||
t0 = t and
|
||||
exists(ap) and
|
||||
not stateBarrier(node, state) and
|
||||
(
|
||||
@@ -2197,8 +2206,8 @@ module Impl<FullStateConfigSig Config> {
|
||||
import BooleanCallContext
|
||||
|
||||
predicate localStep(
|
||||
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
|
||||
DataFlowType t, LocalCc lcc
|
||||
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, Typ t,
|
||||
LocalCc lcc
|
||||
) {
|
||||
localFlowBigStep(node1, state1, node2, state2, preservesValue, t, _) and
|
||||
exists(lcc)
|
||||
@@ -2218,10 +2227,16 @@ module Impl<FullStateConfigSig Config> {
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[node, state, t, ap]
|
||||
predicate filter(NodeEx node, FlowState state, Typ t, Ap ap) {
|
||||
bindingset[node, state, t0, ap]
|
||||
predicate filter(NodeEx node, FlowState state, Typ t0, Ap ap, Typ t) {
|
||||
exists(state) and
|
||||
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), t) else any()) and
|
||||
// We can get away with not using type strengthening here, since we aren't
|
||||
// going to use the tracked types in the construction of Stage 4 access
|
||||
// paths. For Stage 4 and onwards, the tracked types must be consistent as
|
||||
// the cons candidates including types are used to construct subsequent
|
||||
// access path approximations.
|
||||
t0 = t and
|
||||
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), t0) else any()) and
|
||||
(
|
||||
notExpectsContent(node)
|
||||
or
|
||||
@@ -2241,6 +2256,16 @@ module Impl<FullStateConfigSig Config> {
|
||||
import MkStage<Stage2>::Stage<Stage3Param>
|
||||
}
|
||||
|
||||
bindingset[node, t0]
|
||||
private predicate strengthenType(NodeEx node, DataFlowType t0, DataFlowType t) {
|
||||
if castingNodeEx(node)
|
||||
then
|
||||
exists(DataFlowType nt | nt = node.getDataFlowType() |
|
||||
if typeStrongerThan(nt, t0) then t = nt else (compatibleTypes(nt, t0) and t = t0)
|
||||
)
|
||||
else t = t0
|
||||
}
|
||||
|
||||
private module Stage4Param implements MkStage<Stage3>::StageParam {
|
||||
private module PrevStage = Stage3;
|
||||
|
||||
@@ -2274,8 +2299,8 @@ module Impl<FullStateConfigSig Config> {
|
||||
|
||||
pragma[nomagic]
|
||||
predicate localStep(
|
||||
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
|
||||
DataFlowType t, LocalCc lcc
|
||||
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, Typ t,
|
||||
LocalCc lcc
|
||||
) {
|
||||
localFlowBigStep(node1, state1, node2, state2, preservesValue, t, _) and
|
||||
PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and
|
||||
@@ -2333,11 +2358,11 @@ module Impl<FullStateConfigSig Config> {
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[node, state, t, ap]
|
||||
predicate filter(NodeEx node, FlowState state, Typ t, Ap ap) {
|
||||
bindingset[node, state, t0, ap]
|
||||
predicate filter(NodeEx node, FlowState state, Typ t0, Ap ap, Typ t) {
|
||||
exists(state) and
|
||||
not clear(node, ap) and
|
||||
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), t) else any()) and
|
||||
strengthenType(node, t0, t) and
|
||||
(
|
||||
notExpectsContent(node)
|
||||
or
|
||||
@@ -2365,7 +2390,7 @@ module Impl<FullStateConfigSig Config> {
|
||||
exists(AccessPathFront apf |
|
||||
Stage4::revFlow(node, state, TReturnCtxMaybeFlowThrough(_), _, apf) and
|
||||
Stage4::fwdFlow(node, state, any(Stage4::CcCall ccc), _, _, TAccessPathFrontSome(argApf), _,
|
||||
apf)
|
||||
apf, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2579,8 +2604,8 @@ module Impl<FullStateConfigSig Config> {
|
||||
import LocalCallContext
|
||||
|
||||
predicate localStep(
|
||||
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue,
|
||||
DataFlowType t, LocalCc lcc
|
||||
NodeEx node1, FlowState state1, NodeEx node2, FlowState state2, boolean preservesValue, Typ t,
|
||||
LocalCc lcc
|
||||
) {
|
||||
localFlowBigStep(node1, state1, node2, state2, preservesValue, t, lcc) and
|
||||
PrevStage::revFlow(node1, pragma[only_bind_into](state1), _) and
|
||||
@@ -2609,9 +2634,9 @@ module Impl<FullStateConfigSig Config> {
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[node, state, t, ap]
|
||||
predicate filter(NodeEx node, FlowState state, Typ t, Ap ap) {
|
||||
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), t) else any()) and
|
||||
bindingset[node, state, t0, ap]
|
||||
predicate filter(NodeEx node, FlowState state, Typ t0, Ap ap, Typ t) {
|
||||
strengthenType(node, t0, t) and
|
||||
exists(state) and
|
||||
exists(ap)
|
||||
}
|
||||
@@ -2632,7 +2657,7 @@ module Impl<FullStateConfigSig Config> {
|
||||
Stage5::parameterMayFlowThrough(p, _) and
|
||||
Stage5::revFlow(n, state, TReturnCtxMaybeFlowThrough(_), _, apa0) and
|
||||
Stage5::fwdFlow(n, state, any(CallContextCall ccc), TParamNodeSome(p.asNode()), _,
|
||||
TAccessPathApproxSome(apa), _, apa0)
|
||||
TAccessPathApproxSome(apa), _, apa0, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2649,7 +2674,7 @@ module Impl<FullStateConfigSig Config> {
|
||||
TSummaryCtxSome(ParamNodeEx p, FlowState state, DataFlowType t, AccessPath ap) {
|
||||
exists(AccessPathApprox apa | ap.getApprox() = apa |
|
||||
Stage5::parameterMayFlowThrough(p, apa) and
|
||||
Stage5::fwdFlow(p, state, _, _, _, _, t, apa) and
|
||||
Stage5::fwdFlow(p, state, _, _, Option<DataFlowType>::some(t), _, _, apa, _) and
|
||||
Stage5::revFlow(p, state, _)
|
||||
)
|
||||
}
|
||||
@@ -2820,9 +2845,7 @@ module Impl<FullStateConfigSig Config> {
|
||||
ap = TAccessPathNil()
|
||||
or
|
||||
// ... or a step from an existing PathNode to another node.
|
||||
pathStep(_, node, state, cc, sc, t, ap) and
|
||||
Stage5::revFlow(node, state, ap.getApprox()) and
|
||||
(if castingNodeEx(node) then compatibleTypes(node.getDataFlowType(), t) else any())
|
||||
pathStep(_, node, state, cc, sc, t, ap)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, FlowState state) {
|
||||
exists(PathNodeMid sink |
|
||||
@@ -3340,13 +3363,24 @@ module Impl<FullStateConfigSig Config> {
|
||||
ap = mid.getAp()
|
||||
}
|
||||
|
||||
private predicate pathStep(
|
||||
PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, DataFlowType t,
|
||||
AccessPath ap
|
||||
) {
|
||||
exists(DataFlowType t0 |
|
||||
pathStep0(mid, node, state, cc, sc, t0, ap) and
|
||||
Stage5::revFlow(node, state, ap.getApprox()) and
|
||||
strengthenType(node, t0, t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `mid` to `node`. The last step in or out of
|
||||
* a callable is recorded by `cc`.
|
||||
*/
|
||||
pragma[assume_small_delta]
|
||||
pragma[nomagic]
|
||||
private predicate pathStep(
|
||||
private predicate pathStep0(
|
||||
PathNodeMid mid, NodeEx node, FlowState state, CallContext cc, SummaryCtx sc, DataFlowType t,
|
||||
AccessPath ap
|
||||
) {
|
||||
@@ -3964,7 +3998,7 @@ module Impl<FullStateConfigSig Config> {
|
||||
ap = TPartialNil() and
|
||||
exists(explorationLimit())
|
||||
or
|
||||
partialPathNodeMk0(node, state, cc, sc1, sc2, sc3, sc4, t, ap) and
|
||||
partialPathStep(_, node, state, cc, sc1, sc2, sc3, sc4, t, ap) and
|
||||
distSrc(node.getEnclosingCallable()) <= explorationLimit()
|
||||
} or
|
||||
TPartialPathNodeRev(
|
||||
@@ -3990,11 +4024,20 @@ module Impl<FullStateConfigSig Config> {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate partialPathNodeMk0(
|
||||
NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1, TSummaryCtx2 sc2,
|
||||
TSummaryCtx3 sc3, TSummaryCtx4 sc4, DataFlowType t, PartialAccessPath ap
|
||||
private predicate partialPathStep(
|
||||
PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1,
|
||||
TSummaryCtx2 sc2, TSummaryCtx3 sc3, TSummaryCtx4 sc4, DataFlowType t, PartialAccessPath ap
|
||||
) {
|
||||
partialPathStep(_, node, state, cc, sc1, sc2, sc3, sc4, t, ap) and
|
||||
partialPathStep1(mid, node, state, cc, sc1, sc2, sc3, sc4, _, t, ap)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate partialPathStep1(
|
||||
PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1,
|
||||
TSummaryCtx2 sc2, TSummaryCtx3 sc3, TSummaryCtx4 sc4, DataFlowType t0, DataFlowType t,
|
||||
PartialAccessPath ap
|
||||
) {
|
||||
partialPathStep0(mid, node, state, cc, sc1, sc2, sc3, sc4, t0, ap) and
|
||||
not fullBarrier(node) and
|
||||
not stateBarrier(node, state) and
|
||||
not clearsContentEx(node, ap.getHead()) and
|
||||
@@ -4002,9 +4045,14 @@ module Impl<FullStateConfigSig Config> {
|
||||
notExpectsContent(node) or
|
||||
expectsContentEx(node, ap.getHead())
|
||||
) and
|
||||
if node.asNode() instanceof CastingNode
|
||||
then compatibleTypes(node.getDataFlowType(), t)
|
||||
else any()
|
||||
strengthenType(node, t0, t)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate partialPathTypeStrengthen(
|
||||
DataFlowType t0, PartialAccessPath ap, DataFlowType t
|
||||
) {
|
||||
partialPathStep1(_, _, _, _, _, _, _, _, t0, t, ap) and t0 != t
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4183,7 +4231,8 @@ module Impl<FullStateConfigSig Config> {
|
||||
}
|
||||
}
|
||||
|
||||
private predicate partialPathStep(
|
||||
pragma[nomagic]
|
||||
private predicate partialPathStep0(
|
||||
PartialPathNodeFwd mid, NodeEx node, FlowState state, CallContext cc, TSummaryCtx1 sc1,
|
||||
TSummaryCtx2 sc2, TSummaryCtx3 sc3, TSummaryCtx4 sc4, DataFlowType t, PartialAccessPath ap
|
||||
) {
|
||||
@@ -4309,6 +4358,11 @@ module Impl<FullStateConfigSig Config> {
|
||||
DataFlowType t1, PartialAccessPath ap1, Content c, DataFlowType t2, PartialAccessPath ap2
|
||||
) {
|
||||
partialPathStoreStep(_, t1, ap1, c, _, t2, ap2)
|
||||
or
|
||||
exists(DataFlowType t0 |
|
||||
partialPathTypeStrengthen(t0, ap2, t2) and
|
||||
apConsFwd(t1, ap1, c, t0, ap2)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
|
||||
@@ -131,10 +131,10 @@ module LocalFlow {
|
||||
/**
|
||||
* Holds if `nodeFrom` is a parameter node, and `nodeTo` is a corresponding SSA node.
|
||||
*/
|
||||
predicate localFlowSsaParamInput(Node nodeFrom, Node nodeTo) {
|
||||
predicate localFlowSsaParamInput(Node nodeFrom, SsaDefinitionExtNode nodeTo) {
|
||||
nodeTo = getParameterDefNode(nodeFrom.(ParameterNodeImpl).getParameter())
|
||||
or
|
||||
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNode).getMethod())
|
||||
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNodeImpl).getMethod())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,14 +143,13 @@ module LocalFlow {
|
||||
*
|
||||
* This is intended to recover from flow not currently recognised by ordinary capture flow.
|
||||
*/
|
||||
predicate localFlowSsaParamCaptureInput(Node nodeFrom, Node nodeTo) {
|
||||
exists(Ssa::CapturedEntryDefinition def, ParameterNodeImpl p |
|
||||
(nodeFrom = p or LocalFlow::localFlowSsaParamInput(p, nodeFrom)) and
|
||||
predicate localFlowSsaParamCaptureInput(ParameterNodeImpl nodeFrom, Node nodeTo) {
|
||||
exists(Ssa::CapturedEntryDefinition def |
|
||||
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
||||
|
|
||||
p.getParameter().(NamedParameter).getVariable() = def.getSourceVariable()
|
||||
nodeFrom.getParameter().(NamedParameter).getVariable() = def.getSourceVariable()
|
||||
or
|
||||
p.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
|
||||
nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -164,7 +163,7 @@ module LocalFlow {
|
||||
|
||||
/**
|
||||
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
|
||||
* SSA definition `def`.
|
||||
* some SSA definition.
|
||||
*/
|
||||
private predicate localSsaFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(SsaImpl::DefinitionExt def |
|
||||
@@ -182,6 +181,8 @@ module LocalFlow {
|
||||
// Flow into phi (read) SSA definition node from def
|
||||
localFlowSsaInputFromDef(nodeFrom, def, nodeTo)
|
||||
)
|
||||
or
|
||||
localFlowSsaParamInput(nodeFrom, nodeTo)
|
||||
// TODO
|
||||
// or
|
||||
// // Flow into uncertain SSA definition
|
||||
@@ -223,6 +224,13 @@ module LocalFlow {
|
||||
op.getExpr() instanceof BinaryLogicalOperation and
|
||||
nodeFrom.asExpr() = op.getAnOperand()
|
||||
)
|
||||
or
|
||||
nodeTo.(ParameterNodeImpl).getParameter() =
|
||||
any(NamedParameter p |
|
||||
p.(OptionalParameter).getDefaultValue() = nodeFrom.asExpr().getExpr()
|
||||
or
|
||||
p.(KeywordParameter).getDefaultValue() = nodeFrom.asExpr().getExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,12 +287,6 @@ private module Cached {
|
||||
newtype TNode =
|
||||
TExprNode(CfgNodes::ExprCfgNode n) { TaintTrackingPrivate::forceCachingInSameStage() } or
|
||||
TReturningNode(CfgNodes::ReturningCfgNode n) or
|
||||
TSynthReturnNode(CfgScope scope, ReturnKind kind) {
|
||||
exists(ReturningNode ret |
|
||||
ret.(NodeImpl).getCfgScope() = scope and
|
||||
ret.getKind() = kind
|
||||
)
|
||||
} or
|
||||
TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) or
|
||||
TNormalParameterNode(Parameter p) {
|
||||
p instanceof SimpleParameter or
|
||||
@@ -326,12 +328,6 @@ private module Cached {
|
||||
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
|
||||
TSynthHashSplatParameterNode or TSummaryParameterNode;
|
||||
|
||||
private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
|
||||
p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
|
||||
or
|
||||
p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr()
|
||||
}
|
||||
|
||||
cached
|
||||
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
|
||||
|
||||
@@ -346,12 +342,6 @@ private module Cached {
|
||||
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
|
||||
or
|
||||
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
|
||||
or
|
||||
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and
|
||||
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _)
|
||||
or
|
||||
@@ -373,10 +363,6 @@ private module Cached {
|
||||
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
defaultValueFlow(nodeTo.(ParameterNodeImpl).getParameter(), nodeFrom)
|
||||
or
|
||||
LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
|
||||
or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
@@ -386,19 +372,11 @@ private module Cached {
|
||||
|
||||
/**
|
||||
* This is the local flow predicate that is used in type tracking.
|
||||
*
|
||||
* This needs to exclude `localFlowSsaParamInput` due to a performance trick
|
||||
* in type tracking, where such steps are treated as call steps.
|
||||
*/
|
||||
cached
|
||||
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
exists(NamedParameter p |
|
||||
defaultValueFlow(p, nodeFrom) and
|
||||
nodeTo = LocalFlow::getParameterDefNode(p)
|
||||
)
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
|
||||
or
|
||||
// Flow into phi node from read
|
||||
@@ -440,12 +418,10 @@ private module Cached {
|
||||
n instanceof ExprNode and
|
||||
not reachedFromExprOrEntrySsaDef(n)
|
||||
or
|
||||
// Ensure all entry SSA definitions are local sources -- for parameters, this
|
||||
// is needed by type tracking
|
||||
entrySsaDefinition(n)
|
||||
or
|
||||
// Needed for flow out in type tracking
|
||||
n instanceof SynthReturnNode
|
||||
// Ensure all entry SSA definitions are local sources, except those that correspond
|
||||
// to parameters (which are themselves local sources)
|
||||
entrySsaDefinition(n) and
|
||||
not LocalFlow::localFlowSsaParamInput(_, n)
|
||||
or
|
||||
// Needed for stores in type tracking
|
||||
TypeTrackerSpecific::storeStepIntoSourceNode(_, n, _)
|
||||
@@ -507,7 +483,7 @@ private module Cached {
|
||||
*/
|
||||
cached
|
||||
predicate exprNodeReturnedFromCached(ExprNode e, Callable c) {
|
||||
exists(ReturningNode r |
|
||||
exists(ReturnNode r |
|
||||
nodeGetEnclosingCallable(r).asCallable() = c and
|
||||
(
|
||||
r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or
|
||||
@@ -542,8 +518,6 @@ predicate nodeIsHidden(Node n) {
|
||||
or
|
||||
n instanceof SummaryParameterNode
|
||||
or
|
||||
n instanceof SynthReturnNode
|
||||
or
|
||||
n instanceof SynthHashSplatParameterNode
|
||||
or
|
||||
n instanceof SynthHashSplatArgumentNode
|
||||
@@ -658,10 +632,10 @@ private module ParameterNodes {
|
||||
* The value of the `self` parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class SelfParameterNode extends ParameterNodeImpl, TSelfParameterNode {
|
||||
class SelfParameterNodeImpl extends ParameterNodeImpl, TSelfParameterNode {
|
||||
private MethodBase method;
|
||||
|
||||
SelfParameterNode() { this = TSelfParameterNode(method) }
|
||||
SelfParameterNodeImpl() { this = TSelfParameterNode(method) }
|
||||
|
||||
final MethodBase getMethod() { result = method }
|
||||
|
||||
@@ -937,24 +911,26 @@ private class NewCall extends DataFlowCall {
|
||||
NewCall() { this.asCall().getExpr().(MethodCall).getMethodName() = "new" }
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a value syntactically returned by a callable. */
|
||||
abstract class ReturningNode extends Node {
|
||||
/** Gets the kind of this return node. */
|
||||
abstract ReturnKind getKind();
|
||||
|
||||
pragma[nomagic]
|
||||
predicate hasKind(ReturnKind kind, CfgScope scope) {
|
||||
kind = this.getKind() and
|
||||
scope = this.(NodeImpl).getCfgScope()
|
||||
}
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a value returned by a callable. */
|
||||
abstract class ReturnNode extends Node {
|
||||
/** Gets the kind of this return node. */
|
||||
abstract ReturnKind getKind();
|
||||
}
|
||||
|
||||
/** A data-flow node that represents a value returned by a callable. */
|
||||
abstract class SourceReturnNode extends ReturnNode {
|
||||
/** Gets the kind of this return node. */
|
||||
abstract ReturnKind getKindSource(); // only exists to avoid spurious negative recursion
|
||||
|
||||
final override ReturnKind getKind() { result = this.getKindSource() }
|
||||
|
||||
pragma[nomagic]
|
||||
predicate hasKind(ReturnKind kind, CfgScope scope) {
|
||||
kind = this.getKindSource() and
|
||||
scope = this.(NodeImpl).getCfgScope()
|
||||
}
|
||||
}
|
||||
|
||||
private module ReturnNodes {
|
||||
private predicate isValid(CfgNodes::ReturningCfgNode node) {
|
||||
exists(ReturningStmt stmt, Callable scope |
|
||||
@@ -976,14 +952,14 @@ private module ReturnNodes {
|
||||
* A data-flow node that represents an expression explicitly returned by
|
||||
* a callable.
|
||||
*/
|
||||
class ExplicitReturnNode extends ReturningNode, ReturningStatementNode {
|
||||
class ExplicitReturnNode extends SourceReturnNode, ReturningStatementNode {
|
||||
ExplicitReturnNode() {
|
||||
isValid(n) and
|
||||
n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
|
||||
n.getScope() instanceof Callable
|
||||
}
|
||||
|
||||
override ReturnKind getKind() {
|
||||
override ReturnKind getKindSource() {
|
||||
if n.getNode() instanceof BreakStmt
|
||||
then result instanceof BreakReturnKind
|
||||
else
|
||||
@@ -1012,10 +988,10 @@ private module ReturnNodes {
|
||||
* a callable. An implicit return happens when an expression can be the
|
||||
* last thing that is evaluated in the body of the callable.
|
||||
*/
|
||||
class ExprReturnNode extends ReturningNode, ExprNode {
|
||||
class ExprReturnNode extends SourceReturnNode, ExprNode {
|
||||
ExprReturnNode() { exists(Callable c | implicitReturn(c, this) = c.getAStmt()) }
|
||||
|
||||
override ReturnKind getKind() {
|
||||
override ReturnKind getKindSource() {
|
||||
exists(CfgScope scope | scope = this.(NodeImpl).getCfgScope() |
|
||||
if isUserDefinedNew(scope)
|
||||
then result instanceof NewReturnKind
|
||||
@@ -1040,7 +1016,7 @@ private module ReturnNodes {
|
||||
* the implicit `self` reference in `@x` will return data stored in the field
|
||||
* `x` out to the call `C.new`.
|
||||
*/
|
||||
class InitializeReturnNode extends ExprPostUpdateNode, ReturningNode {
|
||||
class InitializeReturnNode extends ExprPostUpdateNode, ReturnNode {
|
||||
InitializeReturnNode() {
|
||||
exists(Method initialize |
|
||||
this.getCfgScope() = initialize and
|
||||
@@ -1053,32 +1029,6 @@ private module ReturnNodes {
|
||||
override ReturnKind getKind() { result instanceof NewReturnKind }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthetic data-flow node for joining flow from different syntactic
|
||||
* returns into a single node.
|
||||
*
|
||||
* This node only exists to avoid computing the product of a large fan-in
|
||||
* with a large fan-out.
|
||||
*/
|
||||
class SynthReturnNode extends NodeImpl, ReturnNode, TSynthReturnNode {
|
||||
private CfgScope scope;
|
||||
private ReturnKind kind;
|
||||
|
||||
SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
|
||||
|
||||
/** Gets a syntactic return node that flows into this synthetic node. */
|
||||
pragma[nomagic]
|
||||
ReturningNode getAnInput() { result.hasKind(kind, scope) }
|
||||
|
||||
override ReturnKind getKind() { result = kind }
|
||||
|
||||
override CfgScope getCfgScope() { result = scope }
|
||||
|
||||
override Location getLocationImpl() { result = scope.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = "return " + kind + " in " + scope }
|
||||
}
|
||||
|
||||
private class SummaryReturnNode extends SummaryNode, ReturnNode {
|
||||
private ReturnKind rk;
|
||||
|
||||
@@ -1281,6 +1231,8 @@ class DataFlowType extends TDataFlowType {
|
||||
string toString() { result = "" }
|
||||
}
|
||||
|
||||
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
|
||||
|
||||
/** Gets the type of `n` used for type pruning. */
|
||||
DataFlowType getNodeType(NodeImpl n) { result = TTodoDataFlowType() and exists(n) }
|
||||
|
||||
@@ -1339,9 +1291,6 @@ private import PostUpdateNodes
|
||||
/** A node that performs a type cast. */
|
||||
class CastNode extends Node {
|
||||
CastNode() {
|
||||
// ensure that actual return nodes are included in the path graph
|
||||
this instanceof ReturningNode
|
||||
or
|
||||
// ensure that all variable assignments are included in the path graph
|
||||
this.(SsaDefinitionExtNode).getDefinitionExt() instanceof Ssa::WriteDefinition
|
||||
}
|
||||
|
||||
@@ -195,10 +195,22 @@ class ParameterNode extends LocalSourceNode, TParameterNode instanceof Parameter
|
||||
/** Gets the parameter corresponding to this node, if any. */
|
||||
final Parameter getParameter() { result = super.getParameter() }
|
||||
|
||||
/** Gets the callable that this parameter belongs to. */
|
||||
final Callable getCallable() { result = super.getCfgScope() }
|
||||
|
||||
/** Gets the name of the parameter, if any. */
|
||||
final string getName() { result = this.getParameter().(NamedParameter).getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of an implicit `self` parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class SelfParameterNode extends ParameterNode instanceof SelfParameterNodeImpl {
|
||||
/** Gets the underlying `self` variable. */
|
||||
final SelfVariable getSelfVariable() { result = super.getSelfVariable() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that is a source of local flow.
|
||||
*/
|
||||
@@ -328,9 +340,6 @@ private module Cached {
|
||||
exists(Node mid | hasLocalSource(mid, source) |
|
||||
localFlowStepTypeTracker(mid, sink)
|
||||
or
|
||||
// Explicitly include the SSA param input step as type-tracking omits this step.
|
||||
LocalFlow::localFlowSsaParamInput(mid, sink)
|
||||
or
|
||||
LocalFlow::localFlowSsaParamCaptureInput(mid, sink)
|
||||
)
|
||||
}
|
||||
@@ -1176,19 +1185,15 @@ class CallableNode extends StmtSequenceNode {
|
||||
result = this.getBlockParameter().getAMethodCall("call")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the canonical return node from this callable.
|
||||
*
|
||||
* Each callable has exactly one such node, and its location may not correspond
|
||||
* to any particular return site - consider using `getAReturningNode` to get nodes
|
||||
* whose locations correspond to return sites.
|
||||
*/
|
||||
Node getReturn() { result.(SynthReturnNode).getCfgScope() = callable }
|
||||
|
||||
/**
|
||||
* Gets a data flow node whose value is about to be returned by this callable.
|
||||
*/
|
||||
Node getAReturningNode() { result = this.getReturn().(SynthReturnNode).getAnInput() }
|
||||
Node getAReturnNode() { result.(ReturnNode).(NodeImpl).getCfgScope() = callable }
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `getAReturnNode` instead.
|
||||
*/
|
||||
deprecated Node getAReturningNode() { result = this.getAReturnNode() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -166,28 +166,21 @@ module Public {
|
||||
SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) }
|
||||
}
|
||||
|
||||
private predicate noComponentSpecific(SummaryComponent sc) {
|
||||
not exists(getComponentSpecific(sc))
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this component used for flow summaries. */
|
||||
private string getComponent(SummaryComponent sc) {
|
||||
result = getComponentSpecific(sc)
|
||||
or
|
||||
noComponentSpecific(sc) and
|
||||
(
|
||||
exists(ArgumentPosition pos |
|
||||
sc = TParameterSummaryComponent(pos) and
|
||||
result = "Parameter[" + getArgumentPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
sc = TArgumentSummaryComponent(pos) and
|
||||
result = "Argument[" + getParameterPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
|
||||
exists(ArgumentPosition pos |
|
||||
sc = TParameterSummaryComponent(pos) and
|
||||
result = "Parameter[" + getArgumentPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
sc = TArgumentSummaryComponent(pos) and
|
||||
result = "Argument[" + getParameterPosition(pos) + "]"
|
||||
)
|
||||
or
|
||||
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this stack used for flow summaries. */
|
||||
|
||||
@@ -204,149 +204,6 @@ private class ActionControllerParamsCall extends ParamsCallImpl {
|
||||
}
|
||||
}
|
||||
|
||||
/** Modeling for `ActionDispatch::Request`. */
|
||||
private module Request {
|
||||
/**
|
||||
* A call to `request` from within a controller. This is an instance of
|
||||
* `ActionDispatch::Request`.
|
||||
*/
|
||||
private class RequestNode extends DataFlow::CallNode {
|
||||
RequestNode() { this = actionControllerInstance().getAMethodCall("request") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request`.
|
||||
*/
|
||||
private class RequestMethodCall extends DataFlow::CallNode {
|
||||
RequestMethodCall() {
|
||||
any(RequestNode r).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver())
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class RequestInputAccess extends RequestMethodCall,
|
||||
Http::Server::RequestInputAccess::Range
|
||||
{
|
||||
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which returns request parameters.
|
||||
*/
|
||||
private class ParametersCall extends RequestInputAccess {
|
||||
ParametersCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
|
||||
"filtered_parameters"
|
||||
]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() {
|
||||
result = Http::Server::parameterInputKind()
|
||||
}
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns part or all of the request path. */
|
||||
private class PathCall extends RequestInputAccess {
|
||||
PathCall() {
|
||||
this.getMethodName() =
|
||||
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns a specific request header. */
|
||||
private class HeadersCall extends RequestInputAccess {
|
||||
HeadersCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
|
||||
"host_authority", "content_type", "host", "hostname", "accept_encoding",
|
||||
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
|
||||
]
|
||||
or
|
||||
// Request headers are prefixed with `HTTP_` to distinguish them from
|
||||
// "headers" supplied by Rack middleware.
|
||||
this.getMethodName() = ["get_header", "fetch_header"] and
|
||||
this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
// TODO: each_header
|
||||
/**
|
||||
* A method call on `request` which returns part or all of the host.
|
||||
* This can be influenced by headers such as Host and X-Forwarded-Host.
|
||||
*/
|
||||
private class HostCall extends RequestInputAccess {
|
||||
HostCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
|
||||
"forwarded_host", "port", "forwarded_port"
|
||||
]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which is influenced by one or more request
|
||||
* headers.
|
||||
*/
|
||||
private class HeaderTaintedCall extends RequestInputAccess {
|
||||
HeaderTaintedCall() {
|
||||
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns the request body. */
|
||||
private class BodyCall extends RequestInputAccess {
|
||||
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
|
||||
}
|
||||
|
||||
private module Env {
|
||||
abstract private class Env extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/**
|
||||
* A method call on `request` which returns the rack env.
|
||||
* This is a hash containing all the information about the request. Values
|
||||
* under keys starting with `HTTP_` are user-controlled.
|
||||
*/
|
||||
private class RequestEnvCall extends DataFlow::CallNode, Env {
|
||||
RequestEnvCall() { this.getMethodName() = ["env", "filtered_env"] }
|
||||
}
|
||||
|
||||
private import codeql.ruby.frameworks.Rack
|
||||
|
||||
private class RackEnv extends Env {
|
||||
RackEnv() { this = any(Rack::AppCandidate app).getEnv().getALocalUse() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A read of a user-controlled parameter from the request env.
|
||||
*/
|
||||
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
|
||||
EnvHttpAccess() {
|
||||
this = any(Env c).getAMethodCall("[]") and
|
||||
exists(string key | key = this.getArgument(0).getConstantValue().getString() |
|
||||
key.regexpMatch("^HTTP_.+") or key = "PATH_INFO"
|
||||
)
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
|
||||
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `render` from within a controller. */
|
||||
private class ActionControllerRenderCall extends RenderCallImpl {
|
||||
ActionControllerRenderCall() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@ private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPrivate
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.Stdlib
|
||||
private import codeql.ruby.frameworks.Core
|
||||
@@ -319,19 +318,19 @@ private class ActiveRecordModelFinderCall extends ActiveRecordModelInstantiation
|
||||
|
||||
// A `self` reference that may resolve to an active record model object
|
||||
private class ActiveRecordModelClassSelfReference extends ActiveRecordModelInstantiation,
|
||||
SsaSelfDefinitionNode
|
||||
DataFlow::SelfParameterNode
|
||||
{
|
||||
private ActiveRecordModelClass cls;
|
||||
|
||||
ActiveRecordModelClassSelfReference() {
|
||||
exists(MethodBase m |
|
||||
m = this.getCfgScope() and
|
||||
m = this.getCallable() and
|
||||
m.getEnclosingModule() = cls and
|
||||
m = cls.getAMethod()
|
||||
) and
|
||||
// In a singleton method, `self` refers to the class itself rather than an
|
||||
// instance of that class
|
||||
not this.getSelfScope() instanceof SingletonMethod
|
||||
not this.getSelfVariable().getDeclaringScope() instanceof SingletonMethod
|
||||
}
|
||||
|
||||
final override ActiveRecordModelClass getClass() { result = cls }
|
||||
|
||||
73
ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll
Normal file
73
ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Provides modeling for mysql2, a Ruby library (gem) for interacting with MySql databases.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for mysql2, a Ruby library (gem) for interacting with MySql databases.
|
||||
*/
|
||||
module Mysql2 {
|
||||
/**
|
||||
* Flow summary for `Mysql2::Client.new()`.
|
||||
*/
|
||||
private class SqlSummary extends SummarizedCallable {
|
||||
SqlSummary() { this = "Mysql2::Client.new()" }
|
||||
|
||||
override MethodCall getACall() { result = any(Mysql2Connection c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to Mysql2::Client.new() is used to establish a connection to a MySql database. */
|
||||
private class Mysql2Connection extends DataFlow::CallNode {
|
||||
Mysql2Connection() {
|
||||
this = API::getTopLevelMember("Mysql2").getMember("Client").getAnInstantiation()
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that executes SQL statements against a MySQL database. */
|
||||
private class Mysql2Execution extends SqlExecution::Range, DataFlow::CallNode {
|
||||
private DataFlow::Node query;
|
||||
|
||||
Mysql2Execution() {
|
||||
exists(Mysql2Connection mysql2Connection |
|
||||
this = mysql2Connection.getAMethodCall("query") and query = this.getArgument(0)
|
||||
or
|
||||
exists(DataFlow::CallNode prepareCall |
|
||||
prepareCall = mysql2Connection.getAMethodCall("prepare") and
|
||||
query = prepareCall.getArgument(0) and
|
||||
this = prepareCall.getAMethodCall("execute")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = query }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Mysql2::Client.escape`, considered as a sanitizer for SQL statements.
|
||||
*/
|
||||
private class Mysql2EscapeSanitization extends SqlSanitization::Range {
|
||||
Mysql2EscapeSanitization() {
|
||||
this = API::getTopLevelMember("Mysql2").getMember("Client").getAMethodCall("escape")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow summary for `Mysql2::Client.escape()`.
|
||||
*/
|
||||
private class EscapeSummary extends SummarizedCallable {
|
||||
EscapeSummary() { this = "Mysql2::Client.escape()" }
|
||||
|
||||
override MethodCall getACall() { result = any(Mysql2EscapeSanitization c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ module Rack {
|
||||
AppCandidate() {
|
||||
call = this.getInstanceMethod("call") and
|
||||
call.getNumberOfParameters() = 1 and
|
||||
call.getReturn() = trackRackResponse()
|
||||
call.getAReturnNode() = trackRackResponse()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
71
ruby/ql/lib/codeql/ruby/frameworks/Sequel.qll
Normal file
71
ruby/ql/lib/codeql/ruby/frameworks/Sequel.qll
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Provides modeling for `Sequel`, the database toolkit for Ruby.
|
||||
* https://github.com/jeremyevans/sequel
|
||||
*/
|
||||
|
||||
private import ruby
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.dataflow.FlowSummary
|
||||
private import codeql.ruby.Concepts
|
||||
|
||||
/**
|
||||
* Provides modeling for `Sequel`, the database toolkit for Ruby.
|
||||
* https://github.com/jeremyevans/sequel
|
||||
*/
|
||||
module Sequel {
|
||||
/** Flow Summary for `Sequel`. */
|
||||
private class SqlSummary extends SummarizedCallable {
|
||||
SqlSummary() { this = "Sequel.connect" }
|
||||
|
||||
override MethodCall getACall() { result = any(SequelConnection c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to establish a connection to a database */
|
||||
private class SequelConnection extends DataFlow::CallNode {
|
||||
SequelConnection() {
|
||||
this =
|
||||
API::getTopLevelMember("Sequel").getAMethodCall(["connect", "sqlite", "mysql2", "jdbc"])
|
||||
}
|
||||
}
|
||||
|
||||
/** A call that constructs SQL statements */
|
||||
private class SequelConstruction extends SqlConstruction::Range, DataFlow::CallNode {
|
||||
DataFlow::Node query;
|
||||
|
||||
SequelConstruction() {
|
||||
this = API::getTopLevelMember("Sequel").getAMethodCall("cast") and query = this.getArgument(1)
|
||||
or
|
||||
this = API::getTopLevelMember("Sequel").getAMethodCall("function") and
|
||||
query = this.getArgument(0)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = query }
|
||||
}
|
||||
|
||||
/** A call that executes SQL statements against a database */
|
||||
private class SequelExecution extends SqlExecution::Range, DataFlow::CallNode {
|
||||
SequelExecution() {
|
||||
exists(SequelConnection sequelConnection |
|
||||
this =
|
||||
sequelConnection
|
||||
.getAMethodCall([
|
||||
"execute", "execute_ddl", "execute_dui", "execute_insert", "run", "<<", "fetch",
|
||||
"fetch_rows", "[]", "log_connection_yield"
|
||||
]) or
|
||||
this =
|
||||
sequelConnection
|
||||
.getAMethodCall("dataset")
|
||||
.getAMethodCall([
|
||||
"with_sql", "with_sql_all", "with_sql_delete", "with_sql_each", "with_sql_first",
|
||||
"with_sql_insert", "with_sql_single_value", "with_sql_update"
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
}
|
||||
@@ -77,4 +77,26 @@ module Sqlite3 {
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `SQLite3::Database.quote`, considered as a sanitizer for SQL statements.
|
||||
*/
|
||||
private class SQLite3QuoteSanitization extends SqlSanitization {
|
||||
SQLite3QuoteSanitization() {
|
||||
this = API::getTopLevelMember("SQLite3").getMember("Database").getAMethodCall("quote")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow summary for `SQLite3::Database.quote()`.
|
||||
*/
|
||||
private class QuoteSummary extends SummarizedCallable {
|
||||
QuoteSummary() { this = "SQLite3::Database.quote()" }
|
||||
|
||||
override MethodCall getACall() { result = any(SQLite3QuoteSanitization c).asExpr().getExpr() }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* This module is deprecated, and exists as a shim to support any existing code that relies on it.
|
||||
* New code should use `codeql.ruby.frameworks.Core` and `codeql.ruby.frameworks.Stdlib` instead.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.frameworks.Core as Core
|
||||
private import codeql.ruby.frameworks.Stdlib as Stdlib
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class SubshellLiteralExecution = Core::SubshellLiteralExecution;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class SubshellHeredocExecution = Core::SubshellHeredocExecution;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class BasicObjectInstanceMethodCall = Core::BasicObjectInstanceMethodCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated predicate basicObjectInstanceMethodName = Core::basicObjectInstanceMethodName/0;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class InstanceEvalCallCodeExecution = Core::InstanceEvalCallCodeExecution;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class ObjectInstanceMethodCall = Core::ObjectInstanceMethodCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated predicate objectInstanceMethodName = Core::objectInstanceMethodName/0;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class KernelMethodCall = Core::KernelMethodCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class KernelSystemCall = Core::KernelSystemCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class KernelExecCall = Core::KernelExecCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class KernelSpawnCall = Core::KernelSpawnCall;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class EvalCallCodeExecution = Core::EvalCallCodeExecution;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated class SendCallCodeExecution = Core::SendCallCodeExecution;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated module Module = Core::Module;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Core` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated module Array = Core::Array;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Stdlib` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated module Regexp = Core::Regexp;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Stdlib` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated module Open3 = Stdlib::Open3;
|
||||
|
||||
/**
|
||||
* DEPRECATED: Import `codeql.ruby.frameworks.Stdlib` instead of `codeql.ruby.frameworks.StandardLibrary`.
|
||||
*/
|
||||
deprecated module Logger = Stdlib::Logger;
|
||||
@@ -412,9 +412,7 @@ module Filters {
|
||||
/**
|
||||
* Holds if `n` is the self parameter of method `m`.
|
||||
*/
|
||||
private predicate selfParameter(DataFlowPrivate::SelfParameterNode n, Method m) {
|
||||
m = n.getMethod()
|
||||
}
|
||||
private predicate selfParameter(DataFlow::SelfParameterNode n, Method m) { m = n.getCallable() }
|
||||
|
||||
/**
|
||||
* A class defining additional jump steps arising from filters.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Models MIME type handling using the `ActionDispatch` library, which is part of Rails.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.Regexp as RE
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Models MIME type handling using the `ActionDispatch` library, which is part of Rails.
|
||||
*/
|
||||
module Mime {
|
||||
/**
|
||||
* Type summaries for the `Mime::Type` class, i.e. method calls that produce new
|
||||
* `Mime::Type` instances.
|
||||
*/
|
||||
private class MimeTypeTypeSummary extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// type1;type2;path
|
||||
row =
|
||||
[
|
||||
// Mime[type] : Mime::Type (omitted)
|
||||
// Method names with brackets like [] cannot be represented in MaD.
|
||||
// Mime.fetch(type) : Mime::Type
|
||||
"Mime::Type;Mime!;Method[fetch].ReturnValue",
|
||||
// Mime::Type.lookup(str) : Mime::Type
|
||||
"Mime::Type;Mime::Type!;Method[lookup].ReturnValue",
|
||||
// Mime::Type.lookup_by_extension(str) : Mime::Type
|
||||
"Mime::Type;Mime::Type!;Method[lookup_by_extension].ReturnValue",
|
||||
// Mime::Type.register(str) : Mime::Type
|
||||
"Mime::Type;Mime::Type!;Method[register].ReturnValue",
|
||||
// Mime::Type.register_alias(str) : Mime::Type
|
||||
"Mime::Type;Mime::Type!;Method[register_alias].ReturnValue",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to `Mime::Type#match?`, which is converted to a RegExp via
|
||||
* `Regexp.new`.
|
||||
*/
|
||||
class MimeTypeMatchRegExpInterpretation extends RE::RegExpInterpretation::Range {
|
||||
MimeTypeMatchRegExpInterpretation() {
|
||||
this = ModelOutput::getATypeNode("Mime::Type").getAMethodCall(["match?", "=~"]).getArgument(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/** Modeling for `ActionDispatch::Request`. */
|
||||
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.frameworks.ActionController
|
||||
|
||||
/** Modeling for `ActionDispatch::Request`. */
|
||||
module Request {
|
||||
/**
|
||||
* A method call against an `ActionDispatch::Request` instance.
|
||||
*/
|
||||
private class RequestMethodCall extends DataFlow::CallNode {
|
||||
RequestMethodCall() {
|
||||
any(ActionControllerClass cls)
|
||||
.getSelf()
|
||||
.getAMethodCall("request")
|
||||
.(DataFlow::LocalSourceNode)
|
||||
.flowsTo(this.getReceiver()) or
|
||||
this =
|
||||
API::getTopLevelMember("ActionDispatch")
|
||||
.getMember("Request")
|
||||
.getInstance()
|
||||
.getAMethodCall(_)
|
||||
}
|
||||
}
|
||||
|
||||
abstract private class RequestInputAccess extends RequestMethodCall,
|
||||
Http::Server::RequestInputAccess::Range
|
||||
{
|
||||
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which returns request parameters.
|
||||
*/
|
||||
private class ParametersCall extends RequestInputAccess {
|
||||
ParametersCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
|
||||
"filtered_parameters"
|
||||
]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() {
|
||||
result = Http::Server::parameterInputKind()
|
||||
}
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns part or all of the request path. */
|
||||
private class PathCall extends RequestInputAccess {
|
||||
PathCall() {
|
||||
this.getMethodName() =
|
||||
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns a specific request header. */
|
||||
private class HeadersCall extends RequestInputAccess {
|
||||
HeadersCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
|
||||
"host_authority", "content_type", "host", "hostname", "accept_encoding",
|
||||
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
|
||||
]
|
||||
or
|
||||
// Request headers are prefixed with `HTTP_` to distinguish them from
|
||||
// "headers" supplied by Rack middleware.
|
||||
this.getMethodName() = ["get_header", "fetch_header"] and
|
||||
this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
// TODO: each_header
|
||||
/**
|
||||
* A method call on `request` which returns part or all of the host.
|
||||
* This can be influenced by headers such as Host and X-Forwarded-Host.
|
||||
*/
|
||||
private class HostCall extends RequestInputAccess {
|
||||
HostCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
|
||||
"forwarded_host", "port", "forwarded_port"
|
||||
]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on `request` which is influenced by one or more request
|
||||
* headers.
|
||||
*/
|
||||
private class HeaderTaintedCall extends RequestInputAccess {
|
||||
HeaderTaintedCall() {
|
||||
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
}
|
||||
|
||||
/** A method call on `request` which returns the request body. */
|
||||
private class BodyCall extends RequestInputAccess {
|
||||
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
|
||||
}
|
||||
|
||||
private module Env {
|
||||
abstract private class Env extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/**
|
||||
* A method call on `request` which returns the rack env.
|
||||
* This is a hash containing all the information about the request. Values
|
||||
* under keys starting with `HTTP_` are user-controlled.
|
||||
*/
|
||||
private class RequestEnvCall extends DataFlow::CallNode, Env {
|
||||
RequestEnvCall() { this.getMethodName() = ["env", "filtered_env"] }
|
||||
}
|
||||
|
||||
private import codeql.ruby.frameworks.Rack
|
||||
|
||||
private class RackEnv extends Env {
|
||||
RackEnv() { this = any(Rack::AppCandidate app).getEnv().getALocalUse() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A read of a user-controlled parameter from the request env.
|
||||
*/
|
||||
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
|
||||
EnvHttpAccess() {
|
||||
this = any(Env c).getAMethodCall("[]") and
|
||||
exists(string key | key = this.getArgument(0).getConstantValue().getString() |
|
||||
key.regexpMatch("^HTTP_.+") or key = "PATH_INFO"
|
||||
)
|
||||
}
|
||||
|
||||
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
|
||||
|
||||
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,961 @@
|
||||
/**
|
||||
* Models routing configuration specified using the `ActionDispatch` library, which is part of Rails.
|
||||
*/
|
||||
|
||||
private import codeql.ruby.AST
|
||||
|
||||
/**
|
||||
* Models routing configuration specified using the `ActionDispatch` library, which is part of Rails.
|
||||
*/
|
||||
module Routing {
|
||||
/**
|
||||
* A block that defines some routes.
|
||||
* Route blocks can contribute to the path or controller namespace of their child routes.
|
||||
* For example, in the block below
|
||||
* ```rb
|
||||
* scope path: "/admin" do
|
||||
* get "/dashboard", to: "admin_dashboard#show"
|
||||
* end
|
||||
* ```
|
||||
* the route defined by the call to `get` has the full path `/admin/dashboard`.
|
||||
* We track these contributions via `getPathComponent` and `getControllerComponent`.
|
||||
*/
|
||||
abstract private class RouteBlock extends TRouteBlock {
|
||||
/**
|
||||
* Gets the name of a primary CodeQL class to which this route block belongs.
|
||||
*/
|
||||
string getAPrimaryQlClass() { result = "RouteBlock" }
|
||||
|
||||
/**
|
||||
* Gets a string representation of this route block.
|
||||
*/
|
||||
string toString() { none() }
|
||||
|
||||
/**
|
||||
* Gets a `Stmt` within this route block.
|
||||
*/
|
||||
abstract Stmt getAStmt();
|
||||
|
||||
/**
|
||||
* Gets the parent of this route block, if one exists.
|
||||
*/
|
||||
abstract RouteBlock getParent();
|
||||
|
||||
/**
|
||||
* Gets the `n`th parent of this route block.
|
||||
* The zeroth parent is this block, the first parent is the direct parent of this block, etc.
|
||||
*/
|
||||
RouteBlock getParent(int n) {
|
||||
if n = 0 then result = this else result = this.getParent().getParent(n - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the component of the path defined by this block, if it exists.
|
||||
*/
|
||||
abstract string getPathComponent();
|
||||
|
||||
/**
|
||||
* Gets the component of the controller namespace defined by this block, if it exists.
|
||||
*/
|
||||
abstract string getControllerComponent();
|
||||
|
||||
/**
|
||||
* Gets the location of this route block.
|
||||
*/
|
||||
abstract Location getLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block that is not the top-level block.
|
||||
* This block will always have a parent.
|
||||
*/
|
||||
abstract private class NestedRouteBlock extends RouteBlock {
|
||||
RouteBlock parent;
|
||||
|
||||
override RouteBlock getParent() { result = parent }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "NestedRouteBlock" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A top-level routes block.
|
||||
* ```rb
|
||||
* Rails.application.routes.draw do
|
||||
* ...
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private class TopLevelRouteBlock extends RouteBlock, TTopLevelRouteBlock {
|
||||
MethodCall call;
|
||||
// Routing blocks create scopes which define the namespace for controllers and paths,
|
||||
// though they can be overridden in various ways.
|
||||
// The namespaces can differ, so we track them separately.
|
||||
Block block;
|
||||
|
||||
TopLevelRouteBlock() { this = TTopLevelRouteBlock(_, call, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "TopLevelRouteBlock" }
|
||||
|
||||
Block getBlock() { result = block }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
override RouteBlock getParent() { none() }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
|
||||
override string getPathComponent() { none() }
|
||||
|
||||
override string getControllerComponent() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block defined by a call to `constraints`.
|
||||
* ```rb
|
||||
* constraints(foo: /some_regex/) do
|
||||
* get "/posts/:foo", to "posts#something"
|
||||
* end
|
||||
* ```
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-constraints
|
||||
*/
|
||||
private class ConstraintsRouteBlock extends NestedRouteBlock, TConstraintsRouteBlock {
|
||||
private Block block;
|
||||
private MethodCall call;
|
||||
|
||||
ConstraintsRouteBlock() { this = TConstraintsRouteBlock(parent, call, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ConstraintsRouteBlock" }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
override string getPathComponent() { result = "" }
|
||||
|
||||
override string getControllerComponent() { result = "" }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block defined by a call to `scope`.
|
||||
* ```rb
|
||||
* scope(path: "/some_path", module: "some_module") do
|
||||
* get "/posts/:foo", to "posts#something"
|
||||
* end
|
||||
* ```
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-scope
|
||||
*/
|
||||
private class ScopeRouteBlock extends NestedRouteBlock, TScopeRouteBlock {
|
||||
private MethodCall call;
|
||||
private Block block;
|
||||
|
||||
ScopeRouteBlock() { this = TScopeRouteBlock(parent, call, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ScopeRouteBlock" }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
|
||||
override string getPathComponent() {
|
||||
call.getKeywordArgument("path").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
not exists(call.getKeywordArgument("path")) and
|
||||
call.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getControllerComponent() {
|
||||
call.getKeywordArgument(["controller", "module"]).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block defined by a call to `resources`.
|
||||
* ```rb
|
||||
* resources :articles do
|
||||
* get "/comments", to "comments#index"
|
||||
* end
|
||||
* ```
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Resources.html#method-i-resources
|
||||
*/
|
||||
private class ResourcesRouteBlock extends NestedRouteBlock, TResourcesRouteBlock {
|
||||
private MethodCall call;
|
||||
private Block block;
|
||||
|
||||
ResourcesRouteBlock() { this = TResourcesRouteBlock(parent, call, block) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ResourcesRouteBlock" }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
/**
|
||||
* Gets the `resources` call that gives rise to this route block.
|
||||
*/
|
||||
MethodCall getDefiningMethodCall() { result = call }
|
||||
|
||||
override string getPathComponent() {
|
||||
exists(string resource | call.getArgument(0).getConstantValue().isStringlikeValue(resource) |
|
||||
result = resource + "/:" + singularize(resource) + "_id"
|
||||
)
|
||||
}
|
||||
|
||||
override string getControllerComponent() { result = "" }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block that is guarded by a conditional statement.
|
||||
* For example:
|
||||
* ```rb
|
||||
* if Rails.env.test?
|
||||
* get "/foo/bar", to: "foo#bar"
|
||||
* end
|
||||
* ```
|
||||
* We ignore the condition and analyze both branches to obtain as
|
||||
* much routing information as possible.
|
||||
*/
|
||||
private class ConditionalRouteBlock extends NestedRouteBlock, TConditionalRouteBlock {
|
||||
private ConditionalExpr e;
|
||||
|
||||
ConditionalRouteBlock() { this = TConditionalRouteBlock(parent, e) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ConditionalRouteBlock" }
|
||||
|
||||
override Stmt getAStmt() { result = e.getBranch(_).(StmtSequence).getAStmt() }
|
||||
|
||||
override string getPathComponent() { none() }
|
||||
|
||||
override string getControllerComponent() { none() }
|
||||
|
||||
override string toString() { result = e.toString() }
|
||||
|
||||
override Location getLocation() { result = e.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route block defined by a call to `namespace`.
|
||||
* ```rb
|
||||
* namespace :admin do
|
||||
* resources :posts
|
||||
* end
|
||||
* ```
|
||||
* https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-namespace
|
||||
*/
|
||||
private class NamespaceRouteBlock extends NestedRouteBlock, TNamespaceRouteBlock {
|
||||
private MethodCall call;
|
||||
private Block block;
|
||||
|
||||
NamespaceRouteBlock() { this = TNamespaceRouteBlock(parent, call, block) }
|
||||
|
||||
override Stmt getAStmt() { result = block.getAStmt() }
|
||||
|
||||
override string getPathComponent() { result = this.getNamespace() }
|
||||
|
||||
override string getControllerComponent() { result = this.getNamespace() }
|
||||
|
||||
private string getNamespace() {
|
||||
call.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Location getLocation() { result = call.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route configuration. This defines a combination of HTTP method and URL
|
||||
* path which should be routed to a particular controller-action pair.
|
||||
* This can arise from an explicit call to a routing method, for example:
|
||||
* ```rb
|
||||
* get "/photos", to: "photos#index"
|
||||
* ```
|
||||
* or via a convenience method like `resources`, which defines multiple routes at once:
|
||||
* ```rb
|
||||
* resources :photos
|
||||
* ```
|
||||
*/
|
||||
class Route extends TRoute instanceof RouteImpl {
|
||||
/**
|
||||
* Gets the name of a primary CodeQL class to which this route belongs.
|
||||
*/
|
||||
string getAPrimaryQlClass() { result = "Route" }
|
||||
|
||||
/** Gets a string representation of this route. */
|
||||
string toString() { result = super.toString() }
|
||||
|
||||
/**
|
||||
* Gets the location of the method call that defines this route.
|
||||
*/
|
||||
Location getLocation() { result = super.getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the full controller targeted by this route.
|
||||
*/
|
||||
string getController() { result = super.getController() }
|
||||
|
||||
/**
|
||||
* Gets the action targeted by this route.
|
||||
*/
|
||||
string getAction() { result = super.getAction() }
|
||||
|
||||
/**
|
||||
* Gets the HTTP method of this route.
|
||||
* The result is one of [get, post, put, patch, delete].
|
||||
*/
|
||||
string getHttpMethod() { result = super.getHttpMethod() }
|
||||
|
||||
/**
|
||||
* Gets the full path of the route.
|
||||
*/
|
||||
string getPath() { result = super.getPath() }
|
||||
|
||||
/**
|
||||
* Get a URL capture. This is a wildcard URL segment whose value is placed in `params`.
|
||||
* For example, in
|
||||
* ```ruby
|
||||
* get "/foo/:bar/baz", to: "users#index"
|
||||
* ```
|
||||
* the capture is `:bar`.
|
||||
*/
|
||||
string getACapture() { result = super.getACapture() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation of `Route`.
|
||||
* This class is abstract and is thus kept private so we don't expose it to
|
||||
* users.
|
||||
* Extend this class to add new instances of routes.
|
||||
*/
|
||||
abstract private class RouteImpl extends TRoute {
|
||||
/**
|
||||
* Gets the name of a primary CodeQL class to which this route belongs.
|
||||
*/
|
||||
string getAPrimaryQlClass() { result = "RouteImpl" }
|
||||
|
||||
MethodCall method;
|
||||
|
||||
/** Gets a string representation of this route. */
|
||||
string toString() { result = method.toString() }
|
||||
|
||||
/**
|
||||
* Gets the location of the method call that defines this route.
|
||||
*/
|
||||
Location getLocation() { result = method.getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the method call that defines this route.
|
||||
*/
|
||||
MethodCall getDefiningMethodCall() { result = method }
|
||||
|
||||
/**
|
||||
* Get the last component of the path. For example, in
|
||||
* ```rb
|
||||
* get "/photos", to: "photos#index"
|
||||
* ```
|
||||
* this is `/photos`.
|
||||
* If the string has any interpolations, this predicate will have no result.
|
||||
*/
|
||||
abstract string getLastPathComponent();
|
||||
|
||||
/**
|
||||
* Gets the HTTP method of this route.
|
||||
* The result is one of [get, post, put, patch, delete].
|
||||
*/
|
||||
abstract string getHttpMethod();
|
||||
|
||||
/**
|
||||
* Gets the last controller component.
|
||||
* This is the controller specified in the route itself.
|
||||
*/
|
||||
abstract string getLastControllerComponent();
|
||||
|
||||
/**
|
||||
* Gets a component of the controller.
|
||||
* This behaves identically to `getPathComponent`, but for controller information.
|
||||
*/
|
||||
string getControllerComponent(int n) {
|
||||
if n = 0
|
||||
then result = this.getLastControllerComponent()
|
||||
else result = this.getParentBlock().getParent(n - 1).getControllerComponent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full controller targeted by this route.
|
||||
*/
|
||||
string getController() {
|
||||
result =
|
||||
concat(int n |
|
||||
this.getControllerComponent(n) != ""
|
||||
|
|
||||
this.getControllerComponent(n), "/" order by n desc
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the action targeted by this route.
|
||||
*/
|
||||
abstract string getAction();
|
||||
|
||||
/**
|
||||
* Gets the parent `RouteBlock` of this route.
|
||||
*/
|
||||
abstract RouteBlock getParentBlock();
|
||||
|
||||
/**
|
||||
* Gets a component of the path. Components are numbered from 0 up, where 0
|
||||
* is the last component, 1 is the second-last, etc.
|
||||
* For example, in the following route:
|
||||
*
|
||||
* ```rb
|
||||
* namespace path: "foo" do
|
||||
* namespace path: "bar" do
|
||||
* get "baz", to: "foo#bar
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* the components are:
|
||||
*
|
||||
* | n | component
|
||||
* |---|----------
|
||||
* | 0 | baz
|
||||
* | 1 | bar
|
||||
* | 2 | foo
|
||||
*/
|
||||
string getPathComponent(int n) {
|
||||
if n = 0
|
||||
then result = this.getLastPathComponent()
|
||||
else result = this.getParentBlock().getParent(n - 1).getPathComponent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full path of the route.
|
||||
*/
|
||||
string getPath() {
|
||||
result =
|
||||
concat(int n |
|
||||
this.getPathComponent(n) != ""
|
||||
|
|
||||
// Strip leading and trailing slashes from each path component before combining
|
||||
stripSlashes(this.getPathComponent(n)), "/" order by n desc
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a URL capture. This is a wildcard URL segment whose value is placed in `params`.
|
||||
* For example, in
|
||||
* ```ruby
|
||||
* get "/foo/:bar/baz", to: "users#index"
|
||||
* ```
|
||||
* the capture is `:bar`.
|
||||
* We don't currently make use of this, but it may be useful in future to more accurately
|
||||
* model the contents of the `params` hash.
|
||||
*/
|
||||
string getACapture() { result = this.getPathComponent(_).regexpFind(":[^:/]+", _, _) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route generated by an explicit call to `get`, `post`, etc.
|
||||
*
|
||||
* ```ruby
|
||||
* get "/photos", to: "photos#index"
|
||||
* put "/photos/:id", to: "photos#update"
|
||||
* ```
|
||||
*/
|
||||
private class ExplicitRoute extends RouteImpl, TExplicitRoute {
|
||||
RouteBlock parentBlock;
|
||||
|
||||
ExplicitRoute() { this = TExplicitRoute(parentBlock, method) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ExplicitRoute" }
|
||||
|
||||
override RouteBlock getParentBlock() { result = parentBlock }
|
||||
|
||||
override string getLastPathComponent() {
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
method.getKeywordArgument("controller").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
not exists(method.getKeywordArgument("controller")) and
|
||||
(
|
||||
result = extractController(this.getActionString())
|
||||
or
|
||||
// If controller is not specified, and we're in a `resources` route block, use the controller of that route.
|
||||
// For example, in
|
||||
//
|
||||
// resources :posts do
|
||||
// get "timestamp", to: :timestamp
|
||||
// end
|
||||
//
|
||||
// The route is GET /posts/:post_id/timestamp => posts/timestamp
|
||||
not exists(extractController(this.getActionString())) and
|
||||
exists(ResourcesRoute r |
|
||||
r.getDefiningMethodCall() = parentBlock.(ResourcesRouteBlock).getDefiningMethodCall()
|
||||
|
|
||||
result = r.getLastControllerComponent()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private string getActionString() {
|
||||
method.getKeywordArgument("to").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
method.getKeywordArgument("to").(MethodCall).getMethodName() = "redirect" and
|
||||
result = "<redirect>#<redirect>"
|
||||
}
|
||||
|
||||
override string getAction() {
|
||||
// get "/photos", action: "index"
|
||||
method.getKeywordArgument("action").getConstantValue().isStringlikeValue(result)
|
||||
or
|
||||
not exists(method.getKeywordArgument("action")) and
|
||||
(
|
||||
// get "/photos", to: "photos#index"
|
||||
// get "/photos", to: redirect("some_url")
|
||||
result = extractAction(this.getActionString())
|
||||
or
|
||||
// resources :photos, only: [] do
|
||||
// get "/", to: "index"
|
||||
// end
|
||||
not exists(extractAction(this.getActionString())) and result = this.getActionString()
|
||||
or
|
||||
// get :some_action
|
||||
not exists(this.getActionString()) and
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
)
|
||||
}
|
||||
|
||||
override string getHttpMethod() { result = method.getMethodName().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route generated by a call to `resources`.
|
||||
*
|
||||
* ```ruby
|
||||
* resources :photos
|
||||
* ```
|
||||
* This creates eight routes, equivalent to the following code:
|
||||
* ```ruby
|
||||
* get "/photos", to: "photos#index"
|
||||
* get "/photos/new", to: "photos#new"
|
||||
* post "/photos", to: "photos#create"
|
||||
* get "/photos/:id", to: "photos#show"
|
||||
* get "/photos/:id/edit", to: "photos#edit"
|
||||
* patch "/photos/:id", to: "photos#update"
|
||||
* put "/photos/:id", to: "photos#update"
|
||||
* delete "/photos/:id", to: "photos#delete"
|
||||
* ```
|
||||
*
|
||||
* `resources` can take a block. Any routes defined inside the block will inherit a path component of
|
||||
* `/<resource>/:<resource>_id`. For example:
|
||||
*
|
||||
* ```ruby
|
||||
* resources :photos do
|
||||
* get "/foo", to: "photos#foo"
|
||||
* end
|
||||
* ```
|
||||
* This creates the eight default routes, plus one more, which is nested under "/photos/:photo_id", equivalent to:
|
||||
* ```ruby
|
||||
* get "/photos/:photo_id/foo", to: "photos#foo"
|
||||
* ```
|
||||
*/
|
||||
private class ResourcesRoute extends RouteImpl, TResourcesRoute {
|
||||
RouteBlock parent;
|
||||
string action;
|
||||
string httpMethod;
|
||||
string pathComponent;
|
||||
|
||||
ResourcesRoute() {
|
||||
exists(string resource |
|
||||
this = TResourcesRoute(parent, method, action) and
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(resource) and
|
||||
isDefaultResourceRoute(resource, httpMethod, pathComponent, action)
|
||||
)
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ResourcesRoute" }
|
||||
|
||||
override RouteBlock getParentBlock() { result = parent }
|
||||
|
||||
override string getLastPathComponent() { result = pathComponent }
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getAction() { result = action }
|
||||
|
||||
override string getHttpMethod() { result = httpMethod }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route generated by a call to `resource`.
|
||||
* This is like a `resources` route, but creates routes for a singular resource.
|
||||
* This means there's no index route, no id parameter, and the resource name is expected to be singular.
|
||||
* It will still be routed to a pluralised controller name.
|
||||
* ```ruby
|
||||
* resource :account
|
||||
* ```
|
||||
*/
|
||||
private class SingularResourceRoute extends RouteImpl, TResourceRoute {
|
||||
RouteBlock parent;
|
||||
string action;
|
||||
string httpMethod;
|
||||
string pathComponent;
|
||||
|
||||
SingularResourceRoute() {
|
||||
exists(string resource |
|
||||
this = TResourceRoute(parent, method, action) and
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(resource) and
|
||||
isDefaultSingularResourceRoute(resource, httpMethod, pathComponent, action)
|
||||
)
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "SingularResourceRoute" }
|
||||
|
||||
override RouteBlock getParentBlock() { result = parent }
|
||||
|
||||
override string getLastPathComponent() { result = pathComponent }
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
method.getArgument(0).getConstantValue().isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getAction() { result = action }
|
||||
|
||||
override string getHttpMethod() { result = httpMethod }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route generated by a call to `match`.
|
||||
* This is a lower level primitive that powers `get`, `post` etc.
|
||||
* The first argument can be a path or a (path, controller-action) pair.
|
||||
* The controller, action and HTTP method can be specified with the
|
||||
* `controller:`, `action:` and `via:` keyword arguments, respectively.
|
||||
* ```ruby
|
||||
* match 'photos/:id' => 'photos#show', via: :get
|
||||
* match 'photos/:id', to: 'photos#show', via: :get
|
||||
* match 'photos/:id', to 'photos#show', via: [:get, :post]
|
||||
* match 'photos/:id', controller: 'photos', action: 'show', via: :get
|
||||
* ```
|
||||
*/
|
||||
private class MatchRoute extends RouteImpl, TMatchRoute {
|
||||
private RouteBlock parent;
|
||||
|
||||
MatchRoute() { this = TMatchRoute(parent, method) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "MatchRoute" }
|
||||
|
||||
override RouteBlock getParentBlock() { result = parent }
|
||||
|
||||
override string getLastPathComponent() {
|
||||
[method.getArgument(0), method.getArgument(0).(Pair).getKey()]
|
||||
.getConstantValue()
|
||||
.isStringlikeValue(result)
|
||||
}
|
||||
|
||||
override string getLastControllerComponent() {
|
||||
result =
|
||||
extractController(method.getKeywordArgument("to").getConstantValue().getStringlikeValue()) or
|
||||
method.getKeywordArgument("controller").getConstantValue().isStringlikeValue(result) or
|
||||
result =
|
||||
extractController(method
|
||||
.getArgument(0)
|
||||
.(Pair)
|
||||
.getValue()
|
||||
.getConstantValue()
|
||||
.getStringlikeValue())
|
||||
}
|
||||
|
||||
override string getHttpMethod() {
|
||||
exists(string via |
|
||||
method.getKeywordArgument("via").getConstantValue().isStringlikeValue(via)
|
||||
|
|
||||
via = "all" and result = anyHttpMethod()
|
||||
or
|
||||
via != "all" and result = via
|
||||
)
|
||||
or
|
||||
result =
|
||||
method
|
||||
.getKeywordArgument("via")
|
||||
.(ArrayLiteral)
|
||||
.getElement(_)
|
||||
.getConstantValue()
|
||||
.getStringlikeValue()
|
||||
}
|
||||
|
||||
override string getAction() {
|
||||
result =
|
||||
extractAction(method.getKeywordArgument("to").getConstantValue().getStringlikeValue()) or
|
||||
method.getKeywordArgument("action").getConstantValue().isStringlikeValue(result) or
|
||||
result =
|
||||
extractAction(method
|
||||
.getArgument(0)
|
||||
.(Pair)
|
||||
.getValue()
|
||||
.getConstantValue()
|
||||
.getStringlikeValue())
|
||||
}
|
||||
}
|
||||
|
||||
private import Cached
|
||||
|
||||
/**
|
||||
* This module contains the IPA types backing `RouteBlock` and `Route`, cached for performance.
|
||||
*/
|
||||
cached
|
||||
private module Cached {
|
||||
cached
|
||||
newtype TRouteBlock =
|
||||
TTopLevelRouteBlock(MethodCall routes, MethodCall draw, Block b) {
|
||||
routes.getMethodName() = "routes" and
|
||||
draw.getMethodName() = "draw" and
|
||||
draw.getReceiver() = routes and
|
||||
draw.getBlock() = b
|
||||
} or
|
||||
// constraints(foo: /some_regex/) do
|
||||
// get "/posts/:foo", to "posts#something"
|
||||
// end
|
||||
TConstraintsRouteBlock(RouteBlock parent, MethodCall constraints, Block b) {
|
||||
parent.getAStmt() = constraints and
|
||||
constraints.getMethodName() = "constraints" and
|
||||
constraints.getBlock() = b
|
||||
} or
|
||||
// scope(path: "/some_path", module: "some_module") do
|
||||
// get "/posts/:foo", to "posts#something"
|
||||
// end
|
||||
TScopeRouteBlock(RouteBlock parent, MethodCall scope, Block b) {
|
||||
parent.getAStmt() = scope and scope.getMethodName() = "scope" and scope.getBlock() = b
|
||||
} or
|
||||
// resources :articles do
|
||||
// get "/comments", to "comments#index"
|
||||
// end
|
||||
TResourcesRouteBlock(RouteBlock parent, MethodCall resources, Block b) {
|
||||
parent.getAStmt() = resources and
|
||||
resources.getMethodName() = "resources" and
|
||||
resources.getBlock() = b
|
||||
} or
|
||||
// A conditional statement guarding some routes.
|
||||
// We ignore the condition and analyze both branches to obtain as
|
||||
// much routing information as possible.
|
||||
TConditionalRouteBlock(RouteBlock parent, ConditionalExpr e) { parent.getAStmt() = e } or
|
||||
// namespace :admin do
|
||||
// resources :posts
|
||||
// end
|
||||
TNamespaceRouteBlock(RouteBlock parent, MethodCall namespace, Block b) {
|
||||
parent.getAStmt() = namespace and
|
||||
namespace.getMethodName() = "namespace" and
|
||||
namespace.getBlock() = b
|
||||
}
|
||||
|
||||
/**
|
||||
* A route configuration. See `Route` for more info
|
||||
*/
|
||||
cached
|
||||
newtype TRoute =
|
||||
/**
|
||||
* See `ExplicitRoute`
|
||||
*/
|
||||
TExplicitRoute(RouteBlock b, MethodCall m) {
|
||||
b.getAStmt() = m and m.getMethodName() = anyHttpMethod()
|
||||
} or
|
||||
/**
|
||||
* See `ResourcesRoute`
|
||||
*/
|
||||
TResourcesRoute(RouteBlock b, MethodCall m, string action) {
|
||||
b.getAStmt() = m and
|
||||
m.getMethodName() = "resources" and
|
||||
action in ["show", "index", "new", "edit", "create", "update", "destroy"] and
|
||||
applyActionFilters(m, action)
|
||||
} or
|
||||
/**
|
||||
* See `SingularResourceRoute`
|
||||
*/
|
||||
TResourceRoute(RouteBlock b, MethodCall m, string action) {
|
||||
b.getAStmt() = m and
|
||||
m.getMethodName() = "resource" and
|
||||
action in ["show", "new", "edit", "create", "update", "destroy"] and
|
||||
applyActionFilters(m, action)
|
||||
} or
|
||||
/**
|
||||
* See `MatchRoute`
|
||||
*/
|
||||
TMatchRoute(RouteBlock b, MethodCall m) { b.getAStmt() = m and m.getMethodName() = "match" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Several routing methods support the keyword arguments `only:` and `except:`.
|
||||
* - `only:` restricts the set of actions to just those in the argument.
|
||||
* - `except:` removes the given actions from the set.
|
||||
*/
|
||||
bindingset[action]
|
||||
private predicate applyActionFilters(MethodCall m, string action) {
|
||||
// Respect the `only` keyword argument, which restricts the set of actions.
|
||||
(
|
||||
not exists(m.getKeywordArgument("only"))
|
||||
or
|
||||
exists(Expr only | only = m.getKeywordArgument("only") |
|
||||
[only.(ArrayLiteral).getElement(_), only].getConstantValue().isStringlikeValue(action)
|
||||
)
|
||||
) and
|
||||
// Respect the `except` keyword argument, which removes actions from the default set.
|
||||
(
|
||||
not exists(m.getKeywordArgument("except"))
|
||||
or
|
||||
exists(Expr except | except = m.getKeywordArgument("except") |
|
||||
[except.(ArrayLiteral).getElement(_), except].getConstantValue().getStringlikeValue() !=
|
||||
action
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the (resource, method, path, action) combination would be generated by a call to `resources :<resource>`.
|
||||
*/
|
||||
bindingset[resource]
|
||||
private predicate isDefaultResourceRoute(
|
||||
string resource, string method, string path, string action
|
||||
) {
|
||||
action = "create" and
|
||||
(method = "post" and path = "/" + resource)
|
||||
or
|
||||
action = "index" and
|
||||
(method = "get" and path = "/" + resource)
|
||||
or
|
||||
action = "new" and
|
||||
(method = "get" and path = "/" + resource + "/new")
|
||||
or
|
||||
action = "edit" and
|
||||
(method = "get" and path = "/" + resource + ":id/edit")
|
||||
or
|
||||
action = "show" and
|
||||
(method = "get" and path = "/" + resource + "/:id")
|
||||
or
|
||||
action = "update" and
|
||||
(method in ["put", "patch"] and path = "/" + resource + "/:id")
|
||||
or
|
||||
action = "destroy" and
|
||||
(method = "delete" and path = "/" + resource + "/:id")
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the (resource, method, path, action) combination would be generated by a call to `resource :<resource>`.
|
||||
*/
|
||||
bindingset[resource]
|
||||
private predicate isDefaultSingularResourceRoute(
|
||||
string resource, string method, string path, string action
|
||||
) {
|
||||
action = "create" and
|
||||
(method = "post" and path = "/" + resource)
|
||||
or
|
||||
action = "new" and
|
||||
(method = "get" and path = "/" + resource + "/new")
|
||||
or
|
||||
action = "edit" and
|
||||
(method = "get" and path = "/" + resource + "/edit")
|
||||
or
|
||||
action = "show" and
|
||||
(method = "get" and path = "/" + resource)
|
||||
or
|
||||
action = "update" and
|
||||
(method in ["put", "patch"] and path = "/" + resource)
|
||||
or
|
||||
action = "destroy" and
|
||||
(method = "delete" and path = "/" + resource)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the controller from a Rails routing string
|
||||
* ```
|
||||
* extractController("posts#show") = "posts"
|
||||
* ```
|
||||
*/
|
||||
bindingset[input]
|
||||
private string extractController(string input) { result = input.regexpCapture("([^#]+)#.+", 1) }
|
||||
|
||||
/**
|
||||
* Extract the action from a Rails routing string
|
||||
* ```
|
||||
* extractAction("posts#show") = "show"
|
||||
*/
|
||||
bindingset[input]
|
||||
private string extractAction(string input) { result = input.regexpCapture("[^#]+#(.+)", 1) }
|
||||
|
||||
/**
|
||||
* Returns the lowercase name of every HTTP method we support.
|
||||
*/
|
||||
private string anyHttpMethod() { result = ["get", "post", "put", "patch", "delete"] }
|
||||
|
||||
/**
|
||||
* The inverse of `pluralize`. If `input` is a plural word, it returns the
|
||||
* singular version.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* - photos -> photo
|
||||
* - stories -> story
|
||||
* - not_plural -> not_plural
|
||||
*/
|
||||
bindingset[input]
|
||||
private string singularize(string input) {
|
||||
exists(string prefix | prefix = input.regexpCapture("(.*)ies", 1) | result = prefix + "y")
|
||||
or
|
||||
not input.matches("%ies") and
|
||||
exists(string prefix | prefix = input.regexpCapture("(.*)s", 1) | result = prefix)
|
||||
or
|
||||
not input.regexpMatch(".*(ies|s)") and result = input
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a camel-case string to underscore case. Converts `::` to `/`.
|
||||
* This can be used to convert ActiveRecord controller names to a canonical form that matches the routes they handle.
|
||||
* Note: All-uppercase words like `CONSTANT` are not handled correctly.
|
||||
*/
|
||||
bindingset[base]
|
||||
string underscore(string base) {
|
||||
base = "" and result = ""
|
||||
or
|
||||
result =
|
||||
base.charAt(0).toLowerCase() +
|
||||
// Walk along the string, keeping track of the previous character
|
||||
// in order to determine if we've crossed a boundary.
|
||||
// Boundaries are:
|
||||
// - lower case to upper case: B in FooBar
|
||||
// - entering a namespace: B in Foo::Bar
|
||||
concat(int i, string prev, string char |
|
||||
prev = base.charAt(i) and
|
||||
char = base.charAt(i + 1)
|
||||
|
|
||||
any(string s |
|
||||
char.regexpMatch("[A-Za-z0-9]") and
|
||||
if prev = ":"
|
||||
then s = "/" + char.toLowerCase()
|
||||
else
|
||||
if prev.isLowercase() and char.isUppercase()
|
||||
then s = "_" + char.toLowerCase()
|
||||
else s = char.toLowerCase()
|
||||
)
|
||||
order by
|
||||
i
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip leading and trailing forward slashes from the string.
|
||||
*/
|
||||
bindingset[input]
|
||||
private string stripSlashes(string input) {
|
||||
result = input.regexpReplaceAll("^/+(.+)$", "$1").regexpReplaceAll("^(.*[^/])/+$", "$1")
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,3 @@ module ReflectedXss {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for ReflectedXss */
|
||||
deprecated module ReflectedXSS = ReflectedXss;
|
||||
|
||||
@@ -7,6 +7,7 @@ private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.BarrierGuards
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting SQL injection
|
||||
@@ -53,4 +54,6 @@ module SqlInjection {
|
||||
class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
|
||||
StringConstArrayInclusionCallBarrier
|
||||
{ }
|
||||
|
||||
private class SqlSanitizationAsSanitizer extends Sanitizer, SqlSanitization { }
|
||||
}
|
||||
|
||||
@@ -58,6 +58,3 @@ module StoredXss {
|
||||
|
||||
import TaintTracking::Global<Config>
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for StoredXss */
|
||||
deprecated module StoredXSS = StoredXss;
|
||||
|
||||
@@ -243,9 +243,6 @@ private module Shared {
|
||||
or
|
||||
isFlowFromHelperMethod(node1, node2)
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for isAdditionalXssFlowStep */
|
||||
deprecated predicate isAdditionalXSSFlowStep = isAdditionalXssFlowStep/2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,9 +272,6 @@ module ReflectedXss {
|
||||
*/
|
||||
predicate isAdditionalXssTaintStep = Shared::isAdditionalXssFlowStep/2;
|
||||
|
||||
/** DEPRECATED: Alias for isAdditionalXssTaintStep */
|
||||
deprecated predicate isAdditionalXSSTaintStep = isAdditionalXssTaintStep/2;
|
||||
|
||||
/**
|
||||
* A HTTP request input, considered as a flow source.
|
||||
*/
|
||||
@@ -286,15 +280,18 @@ module ReflectedXss {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for ReflectedXss */
|
||||
deprecated module ReflectedXSS = ReflectedXss;
|
||||
|
||||
private module OrmTracking {
|
||||
/**
|
||||
* A data flow configuration to track flow from finder calls to field accesses.
|
||||
*/
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof OrmInstantiation }
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
// We currently only use ORM instances that come from a call site, so restrict the sources
|
||||
// to calls. This works around a performance issue that would arise from using 'self' as a source
|
||||
// in ActiveRecord models. Over time, library models should stop relying on OrmInstantiation and instead
|
||||
// use API graphs or type-tracking the same way we track other types.
|
||||
source instanceof OrmInstantiation and source instanceof DataFlow::CallNode
|
||||
}
|
||||
|
||||
// Select any call receiver and narrow down later
|
||||
predicate isSink(DataFlow::Node sink) { sink = any(DataFlow::CallNode c).getReceiver() }
|
||||
@@ -302,6 +299,8 @@ private module OrmTracking {
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
Shared::isAdditionalXssFlowStep(node1, node2)
|
||||
}
|
||||
|
||||
predicate isBarrierIn(DataFlow::Node node) { node instanceof DataFlow::SelfParameterNode }
|
||||
}
|
||||
|
||||
import DataFlow::Global<Config>
|
||||
@@ -330,9 +329,6 @@ module StoredXss {
|
||||
*/
|
||||
predicate isAdditionalXssTaintStep = Shared::isAdditionalXssFlowStep/2;
|
||||
|
||||
/** DEPRECATED: Alias for isAdditionalXssTaintStep */
|
||||
deprecated predicate isAdditionalXSSTaintStep = isAdditionalXssTaintStep/2;
|
||||
|
||||
private class OrmFieldAsSource extends Source instanceof DataFlow::CallNode {
|
||||
OrmFieldAsSource() {
|
||||
exists(DataFlow::CallNode subSrc |
|
||||
@@ -346,6 +342,3 @@ module StoredXss {
|
||||
private class FileSystemReadAccessAsSource extends Source instanceof FileSystemReadAccess { }
|
||||
// TODO: Consider `FileNameSource` flowing to script tag `src` attributes and similar
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for StoredXss */
|
||||
deprecated module StoredXSS = StoredXss;
|
||||
|
||||
@@ -224,6 +224,50 @@ private module Cached {
|
||||
|
||||
private import Cached
|
||||
|
||||
private predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
stepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
stepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate stepProj(TypeTrackingNode nodeFrom, StepSummary summary) {
|
||||
step(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
bindingset[nodeFrom, t]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private TypeTracker stepInlineLate(TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
stepProj(nodeFrom, summary) and
|
||||
result = t.append(summary) and
|
||||
step(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
smallstepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate smallstepProj(Node nodeFrom, StepSummary summary) {
|
||||
smallstep(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
bindingset[nodeFrom, t]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private TypeTracker smallstepInlineLate(TypeTracker t, Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
smallstepProj(nodeFrom, summary) and
|
||||
result = t.append(summary) and
|
||||
smallstep(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is being written to the `content` of the object in `nodeTo`.
|
||||
*
|
||||
@@ -298,21 +342,50 @@ class StepSummary extends TStepSummary {
|
||||
module StepSummary {
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
* inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* This predicate is inlined, which enables better join-orders when
|
||||
* the call graph construction and type tracking are mutually recursive.
|
||||
* In such cases, non-linear recursion involving `step` will be limited
|
||||
* to non-linear recursion for the parts of `step` that involve the
|
||||
* call graph.
|
||||
* This predicate should normally not be used; consider using `step`
|
||||
* instead.
|
||||
*/
|
||||
predicate stepCall = Cached::stepCall/3;
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* intra-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* This predicate should normally not be used; consider using `step`
|
||||
* instead.
|
||||
*/
|
||||
predicate stepNoCall = Cached::stepNoCall/3;
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
stepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
stepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* This predicate should normally not be used; consider using `step`
|
||||
* instead.
|
||||
*/
|
||||
predicate smallstepNoCall = Cached::smallstepNoCall/3;
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* intra-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* This predicate should normally not be used; consider using `step`
|
||||
* instead.
|
||||
*/
|
||||
predicate smallstepCall = Cached::smallstepCall/3;
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
@@ -320,7 +393,6 @@ module StepSummary {
|
||||
* Unlike `StepSummary::step`, this predicate does not compress
|
||||
* type-preserving steps.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
smallstepNoCall(nodeFrom, nodeTo, summary)
|
||||
or
|
||||
@@ -431,10 +503,7 @@ class TypeTracker extends TTypeTracker {
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(nodeFrom, pragma[only_bind_out](nodeTo), pragma[only_bind_into](summary)) and
|
||||
result = this.append(pragma[only_bind_into](summary))
|
||||
)
|
||||
result = stepInlineLate(this, nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -463,10 +532,7 @@ class TypeTracker extends TTypeTracker {
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
|
||||
result = this.append(summary)
|
||||
)
|
||||
result = smallstepInlineLate(this, nodeFrom, nodeTo)
|
||||
or
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
result = this
|
||||
@@ -481,6 +547,39 @@ module TypeTracker {
|
||||
TypeTracker end() { result.end() }
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate backStepProj(TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
step(_, nodeTo, summary)
|
||||
}
|
||||
|
||||
bindingset[nodeTo, t]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private TypeBackTracker backStepInlineLate(
|
||||
TypeBackTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
||||
) {
|
||||
exists(StepSummary summary |
|
||||
backStepProj(nodeTo, summary) and
|
||||
result = t.prepend(summary) and
|
||||
step(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate backSmallstepProj(TypeTrackingNode nodeTo, StepSummary summary) {
|
||||
smallstep(_, nodeTo, summary)
|
||||
}
|
||||
|
||||
bindingset[nodeTo, t]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private TypeBackTracker backSmallstepInlineLate(TypeBackTracker t, Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
backSmallstepProj(nodeTo, summary) and
|
||||
result = t.prepend(summary) and
|
||||
smallstep(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary of the steps needed to back-track a use of a value to a given dataflow node.
|
||||
*
|
||||
@@ -564,10 +663,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and
|
||||
this = result.prepend(pragma[only_bind_into](summary))
|
||||
)
|
||||
this = backStepInlineLate(result, nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -596,10 +692,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
|
||||
this = result.prepend(summary)
|
||||
)
|
||||
this = backSmallstepInlineLate(result, nodeFrom, nodeTo)
|
||||
or
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
this = result
|
||||
@@ -635,3 +728,169 @@ module TypeBackTracker {
|
||||
*/
|
||||
TypeBackTracker end() { result.end() }
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides logic for constructing a call graph in mutual recursion with type tracking.
|
||||
*
|
||||
* When type tracking is used to construct a call graph, we cannot use the join-order
|
||||
* from `stepInlineLate`, because `step` becomes a recursive call, which means that we
|
||||
* will have a conjunct with 3 recursive calls: the call to `step`, the call to `stepProj`,
|
||||
* and the recursive type tracking call itself. The solution is to split the three-way
|
||||
* non-linear recursion into two non-linear predicates: one that first joins with the
|
||||
* projected `stepCall` relation, followed by a predicate that joins with the full
|
||||
* `stepCall` relation (`stepNoCall` not being recursive, can be join-ordered in the
|
||||
* same way as in `stepInlineLate`).
|
||||
*/
|
||||
module CallGraphConstruction {
|
||||
/** The input to call graph construction. */
|
||||
signature module InputSig {
|
||||
/** A state to track during type tracking. */
|
||||
class State;
|
||||
|
||||
/** Holds if type tracking should start at `start` in state `state`. */
|
||||
predicate start(Node start, State state);
|
||||
|
||||
/**
|
||||
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
|
||||
* which _does not_ depend on the call graph.
|
||||
*
|
||||
* Implementing this predicate using `StepSummary::[small]stepNoCall` yields
|
||||
* standard type tracking.
|
||||
*/
|
||||
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary);
|
||||
|
||||
/**
|
||||
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
|
||||
* which _does_ depend on the call graph.
|
||||
*
|
||||
* Implementing this predicate using `StepSummary::[small]stepCall` yields
|
||||
* standard type tracking.
|
||||
*/
|
||||
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary);
|
||||
|
||||
/** A projection of an element from the state space. */
|
||||
class StateProj;
|
||||
|
||||
/** Gets the projection of `state`. */
|
||||
StateProj stateProj(State state);
|
||||
|
||||
/** Holds if type tracking should stop at `n` when we are tracking projected state `stateProj`. */
|
||||
predicate filter(Node n, StateProj stateProj);
|
||||
}
|
||||
|
||||
/** Provides the `track` predicate for use in call graph construction. */
|
||||
module Make<InputSig Input> {
|
||||
pragma[nomagic]
|
||||
private predicate stepNoCallProj(Node nodeFrom, StepSummary summary) {
|
||||
Input::stepNoCall(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate stepCallProj(Node nodeFrom, StepSummary summary) {
|
||||
Input::stepCall(nodeFrom, _, summary)
|
||||
}
|
||||
|
||||
bindingset[nodeFrom, t]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private TypeTracker stepNoCallInlineLate(
|
||||
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
|
||||
) {
|
||||
exists(StepSummary summary |
|
||||
stepNoCallProj(nodeFrom, summary) and
|
||||
result = t.append(summary) and
|
||||
Input::stepNoCall(nodeFrom, nodeTo, summary)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[state]
|
||||
pragma[inline_late]
|
||||
private Input::StateProj stateProjInlineLate(Input::State state) {
|
||||
result = Input::stateProj(state)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private Node track(Input::State state, TypeTracker t) {
|
||||
t.start() and Input::start(result, state)
|
||||
or
|
||||
exists(Input::StateProj stateProj |
|
||||
stateProj = stateProjInlineLate(state) and
|
||||
not Input::filter(result, stateProj)
|
||||
|
|
||||
exists(TypeTracker t2 | t = stepNoCallInlineLate(t2, track(state, t2), result))
|
||||
or
|
||||
exists(StepSummary summary |
|
||||
// non-linear recursion
|
||||
Input::stepCall(trackCall(state, t, summary), result, summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[t, summary]
|
||||
pragma[inline_late]
|
||||
private TypeTracker appendInlineLate(TypeTracker t, StepSummary summary) {
|
||||
result = t.append(summary)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private Node trackCall(Input::State state, TypeTracker t, StepSummary summary) {
|
||||
exists(TypeTracker t2 |
|
||||
// non-linear recursion
|
||||
result = track(state, t2) and
|
||||
stepCallProj(result, summary) and
|
||||
t = appendInlineLate(t2, summary)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that can be reached from _some_ start node in state `state`. */
|
||||
pragma[nomagic]
|
||||
Node track(Input::State state) { result = track(state, TypeTracker::end()) }
|
||||
}
|
||||
|
||||
/** A simple version of `CallGraphConstruction` that uses standard type tracking. */
|
||||
module Simple {
|
||||
/** The input to call graph construction. */
|
||||
signature module InputSig {
|
||||
/** A state to track during type tracking. */
|
||||
class State;
|
||||
|
||||
/** Holds if type tracking should start at `start` in state `state`. */
|
||||
predicate start(Node start, State state);
|
||||
|
||||
/** Holds if type tracking should stop at `n`. */
|
||||
predicate filter(Node n);
|
||||
}
|
||||
|
||||
/** Provides the `track` predicate for use in call graph construction. */
|
||||
module Make<InputSig Input> {
|
||||
private module I implements CallGraphConstruction::InputSig {
|
||||
private import codeql.util.Unit
|
||||
|
||||
class State = Input::State;
|
||||
|
||||
predicate start(Node start, State state) { Input::start(start, state) }
|
||||
|
||||
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||
StepSummary::stepNoCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||
StepSummary::stepCall(nodeFrom, nodeTo, summary)
|
||||
}
|
||||
|
||||
class StateProj = Unit;
|
||||
|
||||
Unit stateProj(State state) { exists(state) and exists(result) }
|
||||
|
||||
predicate filter(Node n, Unit u) {
|
||||
Input::filter(n) and
|
||||
exists(u)
|
||||
}
|
||||
}
|
||||
|
||||
import CallGraphConstruction::Make<I>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ predicate jumpStep = DataFlowPrivate::jumpStep/2;
|
||||
/** Holds if there is direct flow from `param` to a return. */
|
||||
pragma[nomagic]
|
||||
private predicate flowThrough(DataFlowPublic::ParameterNode param) {
|
||||
exists(DataFlowPrivate::ReturningNode returnNode, DataFlowDispatch::ReturnKind rk |
|
||||
exists(DataFlowPrivate::SourceReturnNode returnNode, DataFlowDispatch::ReturnKind rk |
|
||||
param.flowsTo(returnNode) and
|
||||
returnNode.hasKind(rk, param.(DataFlowPrivate::NodeImpl).getCfgScope())
|
||||
|
|
||||
@@ -213,15 +213,7 @@ predicate callStep(ExprNodes::CallCfgNode call, Node arg, DataFlowPrivate::Param
|
||||
* recursion (or, at best, terrible performance), since identifying calls to library
|
||||
* methods is done using API graphs (which uses type tracking).
|
||||
*/
|
||||
predicate callStep(Node nodeFrom, Node nodeTo) {
|
||||
callStep(_, nodeFrom, nodeTo)
|
||||
or
|
||||
// In normal data-flow, this will be a local flow step. But for type tracking
|
||||
// we model it as a call step, in order to avoid computing a potential
|
||||
// self-cross product of all calls to a function that returns one of its parameters
|
||||
// (only to later filter that flow out using `TypeTracker::append`).
|
||||
DataFlowPrivate::LocalFlow::localFlowSsaParamInput(nodeFrom, nodeTo)
|
||||
}
|
||||
predicate callStep(Node nodeFrom, Node nodeTo) { callStep(_, nodeFrom, nodeTo) }
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` steps to `nodeTo` by being returned from a call.
|
||||
@@ -233,19 +225,13 @@ predicate callStep(Node nodeFrom, Node nodeTo) {
|
||||
predicate returnStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(ExprNodes::CallCfgNode call |
|
||||
nodeFrom instanceof DataFlowPrivate::ReturnNode and
|
||||
not nodeFrom instanceof DataFlowPrivate::InitializeReturnNode and
|
||||
nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = DataFlowDispatch::getTarget(call) and
|
||||
// deliberately do not include `getInitializeTarget`, since calls to `new` should not
|
||||
// get the return value from `initialize`. Any fields being set in the initializer
|
||||
// will reach all reads via `callStep` and `localFieldStep`.
|
||||
nodeTo.asExpr().getNode() = call.getNode()
|
||||
)
|
||||
or
|
||||
// In normal data-flow, this will be a local flow step. But for type tracking
|
||||
// we model it as a returning flow step, in order to avoid computing a potential
|
||||
// self-cross product of all calls to a function that returns one of its parameters
|
||||
// (only to later filter that flow out using `TypeTracker::append`).
|
||||
nodeTo.(DataFlowPrivate::SynthReturnNode).getAnInput() = nodeFrom and
|
||||
not nodeFrom instanceof DataFlowPrivate::InitializeReturnNode
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,28 +429,19 @@ module SummaryTypeTrackerInput implements SummaryTypeTracker::Input {
|
||||
}
|
||||
|
||||
Node parameterOf(Node callable, SummaryComponent param) {
|
||||
exists(
|
||||
DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos,
|
||||
DataFlowPrivate::ParameterNodeImpl p
|
||||
|
|
||||
exists(DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos |
|
||||
param = SummaryComponent::parameter(apos) and
|
||||
DataFlowDispatch::parameterMatch(ppos, apos) and
|
||||
p.isSourceParameterOf(callable.asExpr().getExpr(), ppos) and
|
||||
// We need to include both `p` and the SSA definition for `p`, since in type-tracking
|
||||
// the step from `p` to the SSA definition is considered a call step.
|
||||
result =
|
||||
[p.(DataFlow::Node), DataFlowPrivate::LocalFlow::getParameterDefNode(p.getParameter())]
|
||||
result
|
||||
.(DataFlowPrivate::ParameterNodeImpl)
|
||||
.isSourceParameterOf(callable.asExpr().getExpr(), ppos)
|
||||
)
|
||||
}
|
||||
|
||||
Node returnOf(Node callable, SummaryComponent return) {
|
||||
exists(DataFlowPrivate::SynthReturnNode ret |
|
||||
return = SummaryComponent::return() and
|
||||
ret.getCfgScope() = callable.asExpr().getExpr() and
|
||||
// We need to include both `ret` and `ret.getAnInput()`, since in type-tracking
|
||||
// the step from `ret.getAnInput()` to `ret` is considered a return step.
|
||||
result = [ret.(DataFlow::Node), ret.getAnInput()]
|
||||
)
|
||||
return = SummaryComponent::return() and
|
||||
result.(DataFlowPrivate::ReturnNode).(DataFlowPrivate::NodeImpl).getCfgScope() =
|
||||
callable.asExpr().getExpr()
|
||||
}
|
||||
|
||||
// Relating callables to nodes
|
||||
|
||||
22
ruby/ql/lib/ide-contextual-queries/printCfg.ql
Normal file
22
ruby/ql/lib/ide-contextual-queries/printCfg.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Print CFG
|
||||
* @description Produces a representation of a file's Control Flow Graph.
|
||||
* This query is used by the VS Code extension.
|
||||
* @id rb/print-cfg
|
||||
* @kind graph
|
||||
* @tags ide-contextual-queries/print-cfg
|
||||
*/
|
||||
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared::TestOutput
|
||||
private import codeql.IDEContextual
|
||||
|
||||
/**
|
||||
* Gets the source file to generate a CFG from.
|
||||
*/
|
||||
external string selectedSourceFile();
|
||||
|
||||
class MyRelevantNode extends RelevantNode {
|
||||
MyRelevantNode() {
|
||||
this.getScope().getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-all
|
||||
version: 0.6.2-dev
|
||||
version: 0.6.3-dev
|
||||
groups: ruby
|
||||
extractor: ruby
|
||||
dbscheme: ruby.dbscheme
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.6.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
3
ruby/ql/src/change-notes/released/0.6.2.md
Normal file
3
ruby/ql/src/change-notes/released/0.6.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.6.2
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.1
|
||||
lastReleaseVersion: 0.6.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-queries
|
||||
version: 0.6.2-dev
|
||||
version: 0.6.3-dev
|
||||
groups:
|
||||
- ruby
|
||||
- queries
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
<example>
|
||||
<p>Consider this regular expression:</p>
|
||||
<sample language="ruby">
|
||||
/^_(__|.)+_$/
|
||||
</sample>
|
||||
/^_(__|.)+_$/</sample>
|
||||
<p>
|
||||
Its sub-expression <code>"(__|.)+?"</code> can match the string
|
||||
<code>"__"</code> either by the first alternative <code>"__"</code> to the
|
||||
@@ -21,8 +20,7 @@
|
||||
repetition:
|
||||
</p>
|
||||
<sample language="ruby">
|
||||
/^_(__|[^_])+_$/
|
||||
</sample>
|
||||
/^_(__|[^_])+_$/</sample>
|
||||
</example>
|
||||
<include src="ReDoSReferences.inc.qhelp"/>
|
||||
</qhelp>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* select sink, source, sink, "$@", source, source.toString()
|
||||
* ```
|
||||
*
|
||||
* To declare expecations, you can use the $hasTaintFlow or $hasValueFlow comments within the test source files.
|
||||
* To declare expectations, you can use the $hasTaintFlow or $hasValueFlow comments within the test source files.
|
||||
* Example of the corresponding test file, e.g. test.rb
|
||||
* ```rb
|
||||
* s = source(1)
|
||||
|
||||
@@ -2,7 +2,7 @@ import codeql.ruby.AST
|
||||
import codeql.ruby.dataflow.internal.DataFlowPrivate
|
||||
import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
|
||||
query predicate ret(ReturningNode node) { any() }
|
||||
query predicate ret(SourceReturnNode node) { any() }
|
||||
|
||||
query predicate arg(ArgumentNode n, DataFlowCall call, ArgumentPosition pos) {
|
||||
n.argumentOf(call, pos) and
|
||||
|
||||
@@ -2814,7 +2814,11 @@
|
||||
| file://:0:0:0:0 | parameter position 0 of File.realdirpath | file://:0:0:0:0 | [summary] to write: return (return) in File.realdirpath |
|
||||
| file://:0:0:0:0 | parameter position 0 of File.realpath | file://:0:0:0:0 | [summary] to write: return (return) in File.realpath |
|
||||
| file://:0:0:0:0 | parameter position 0 of Hash[] | file://:0:0:0:0 | [summary] read: argument position 0.any element in Hash[] |
|
||||
| file://:0:0:0:0 | parameter position 0 of Mysql2::Client.escape() | file://:0:0:0:0 | [summary] to write: return (return) in Mysql2::Client.escape() |
|
||||
| file://:0:0:0:0 | parameter position 0 of Mysql2::Client.new() | file://:0:0:0:0 | [summary] to write: return (return) in Mysql2::Client.new() |
|
||||
| file://:0:0:0:0 | parameter position 0 of PG.new() | file://:0:0:0:0 | [summary] to write: return (return) in PG.new() |
|
||||
| file://:0:0:0:0 | parameter position 0 of SQLite3::Database.quote() | file://:0:0:0:0 | [summary] to write: return (return) in SQLite3::Database.quote() |
|
||||
| file://:0:0:0:0 | parameter position 0 of Sequel.connect | file://:0:0:0:0 | [summary] to write: return (return) in Sequel.connect |
|
||||
| file://:0:0:0:0 | parameter position 0 of String.try_convert | file://:0:0:0:0 | [summary] to write: return (return) in String.try_convert |
|
||||
| file://:0:0:0:0 | parameter position 0 of \| | file://:0:0:0:0 | [summary] read: argument position 0.any element in \| |
|
||||
| file://:0:0:0:0 | parameter position 1.. of File.join | file://:0:0:0:0 | [summary] to write: return (return) in File.join |
|
||||
|
||||
@@ -1,129 +1,72 @@
|
||||
track
|
||||
| type_tracker.rb:1:1:10:3 | self (Container) | type tracker without call steps | type_tracker.rb:1:1:10:3 | self (Container) |
|
||||
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:18:1:21:3 | self (positional) |
|
||||
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:18:1:21:3 | self in positional |
|
||||
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:25:1:28:3 | self (keyword) |
|
||||
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker with call steps | type_tracker.rb:25:1:28:3 | self in keyword |
|
||||
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type tracker without call steps | type_tracker.rb:1:1:53:4 | self (type_tracker.rb) |
|
||||
| type_tracker.rb:2:5:5:7 | &block | type tracker without call steps | type_tracker.rb:2:5:5:7 | &block |
|
||||
| type_tracker.rb:2:5:5:7 | field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | field= |
|
||||
| type_tracker.rb:2:5:5:7 | return return in field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= |
|
||||
| type_tracker.rb:2:5:5:7 | return return in field= | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:2:5:5:7 | self (field=) | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:2:5:5:7 | self (field=) | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:2:5:5:7 | self (field=) | type tracker without call steps | type_tracker.rb:2:5:5:7 | self (field=) |
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:2:5:5:7 | self (field=) |
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type tracker without call steps | type_tracker.rb:2:5:5:7 | self in field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker with call steps | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:5:5:7 | return return in field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:3:9:3:23 | call to puts | type tracker without call steps | type_tracker.rb:3:9:3:23 | call to puts |
|
||||
| type_tracker.rb:3:14:3:23 | call to field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:4:9:4:14 | @field | type tracker without call steps | type_tracker.rb:4:9:4:14 | @field |
|
||||
| type_tracker.rb:7:5:9:7 | &block | type tracker without call steps | type_tracker.rb:7:5:9:7 | &block |
|
||||
| type_tracker.rb:7:5:9:7 | field | type tracker without call steps | type_tracker.rb:7:5:9:7 | field |
|
||||
| type_tracker.rb:7:5:9:7 | return return in field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:7:5:9:7 | return return in field | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field |
|
||||
| type_tracker.rb:7:5:9:7 | return return in field | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:7:5:9:7 | self (field) | type tracker without call steps | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:7:5:9:7 | self in field | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:7:5:9:7 | self in field | type tracker without call steps | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:7:5:9:7 | return return in field |
|
||||
| type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:8:9:8:14 | @field | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:12:1:16:3 | &block | type tracker without call steps | type_tracker.rb:12:1:16:3 | &block |
|
||||
| type_tracker.rb:12:1:16:3 | m | type tracker without call steps | type_tracker.rb:12:1:16:3 | m |
|
||||
| type_tracker.rb:12:1:16:3 | return return in m | type tracker without call steps | type_tracker.rb:12:1:16:3 | return return in m |
|
||||
| type_tracker.rb:12:1:16:3 | self (m) | type tracker without call steps | type_tracker.rb:12:1:16:3 | self (m) |
|
||||
| type_tracker.rb:12:1:16:3 | self in m | type tracker with call steps | type_tracker.rb:12:1:16:3 | self (m) |
|
||||
| type_tracker.rb:12:1:16:3 | self in m | type tracker without call steps | type_tracker.rb:12:1:16:3 | self in m |
|
||||
| type_tracker.rb:13:5:13:7 | var | type tracker without call steps | type_tracker.rb:13:5:13:7 | var |
|
||||
| type_tracker.rb:13:11:13:19 | Container | type tracker without call steps | type_tracker.rb:13:11:13:19 | Container |
|
||||
| type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:2:5:5:7 | self (field=) |
|
||||
| type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:2:5:5:7 | self in field= |
|
||||
| type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:13:11:13:23 | call to new | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:13:11:13:23 | call to new | type tracker without call steps | type_tracker.rb:13:11:13:23 | call to new |
|
||||
| type_tracker.rb:14:5:14:7 | [post] var | type tracker with call steps | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:14:5:14:7 | [post] var | type tracker with call steps | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:14:5:14:7 | [post] var | type tracker without call steps | type_tracker.rb:14:5:14:7 | [post] var |
|
||||
| type_tracker.rb:14:5:14:13 | call to field= | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:17:14:23 | "hello" |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps with content attribute field | type_tracker.rb:14:5:14:7 | [post] var |
|
||||
| type_tracker.rb:14:17:14:23 | __synth__0 | type tracker without call steps | type_tracker.rb:14:17:14:23 | __synth__0 |
|
||||
| type_tracker.rb:15:5:15:18 | call to puts | type tracker without call steps | type_tracker.rb:12:1:16:3 | return return in m |
|
||||
| type_tracker.rb:15:5:15:18 | call to puts | type tracker without call steps | type_tracker.rb:15:5:15:18 | call to puts |
|
||||
| type_tracker.rb:15:10:15:18 | call to field | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:18:1:21:3 | &block | type tracker without call steps | type_tracker.rb:18:1:21:3 | &block |
|
||||
| type_tracker.rb:18:1:21:3 | positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | positional |
|
||||
| type_tracker.rb:18:1:21:3 | return return in positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | return return in positional |
|
||||
| type_tracker.rb:18:1:21:3 | return return in positional | type tracker without call steps | type_tracker.rb:23:1:23:16 | call to positional |
|
||||
| type_tracker.rb:18:1:21:3 | self (positional) | type tracker without call steps | type_tracker.rb:18:1:21:3 | self (positional) |
|
||||
| type_tracker.rb:18:1:21:3 | self in positional | type tracker with call steps | type_tracker.rb:18:1:21:3 | self (positional) |
|
||||
| type_tracker.rb:18:1:21:3 | self in positional | type tracker without call steps | type_tracker.rb:18:1:21:3 | self in positional |
|
||||
| type_tracker.rb:18:16:18:17 | p1 | type tracker with call steps | type_tracker.rb:18:16:18:17 | p1 |
|
||||
| type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 |
|
||||
| type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 |
|
||||
| type_tracker.rb:18:16:18:17 | p1 | type tracker without call steps | type_tracker.rb:18:16:18:17 | p1 |
|
||||
| type_tracker.rb:18:20:18:21 | p2 | type tracker with call steps | type_tracker.rb:18:20:18:21 | p2 |
|
||||
| type_tracker.rb:18:20:18:21 | p2 | type tracker without call steps | type_tracker.rb:18:20:18:21 | p2 |
|
||||
| type_tracker.rb:18:20:18:21 | p2 | type tracker without call steps | type_tracker.rb:18:20:18:21 | p2 |
|
||||
| type_tracker.rb:18:20:18:21 | p2 | type tracker without call steps | type_tracker.rb:18:20:18:21 | p2 |
|
||||
| type_tracker.rb:19:5:19:11 | call to puts | type tracker without call steps | type_tracker.rb:19:5:19:11 | call to puts |
|
||||
| type_tracker.rb:20:5:20:11 | call to puts | type tracker without call steps | type_tracker.rb:18:1:21:3 | return return in positional |
|
||||
| type_tracker.rb:20:5:20:11 | call to puts | type tracker without call steps | type_tracker.rb:20:5:20:11 | call to puts |
|
||||
| type_tracker.rb:20:5:20:11 | call to puts | type tracker without call steps | type_tracker.rb:23:1:23:16 | call to positional |
|
||||
| type_tracker.rb:23:1:23:16 | call to positional | type tracker without call steps | type_tracker.rb:23:1:23:16 | call to positional |
|
||||
| type_tracker.rb:23:12:23:12 | 1 | type tracker with call steps | type_tracker.rb:18:16:18:17 | p1 |
|
||||
| type_tracker.rb:23:12:23:12 | 1 | type tracker with call steps | type_tracker.rb:18:16:18:17 | p1 |
|
||||
| type_tracker.rb:23:12:23:12 | 1 | type tracker without call steps | type_tracker.rb:23:12:23:12 | 1 |
|
||||
| type_tracker.rb:23:15:23:15 | 2 | type tracker with call steps | type_tracker.rb:18:20:18:21 | p2 |
|
||||
| type_tracker.rb:23:15:23:15 | 2 | type tracker with call steps | type_tracker.rb:18:20:18:21 | p2 |
|
||||
| type_tracker.rb:23:15:23:15 | 2 | type tracker without call steps | type_tracker.rb:23:15:23:15 | 2 |
|
||||
| type_tracker.rb:25:1:28:3 | &block | type tracker without call steps | type_tracker.rb:25:1:28:3 | &block |
|
||||
| type_tracker.rb:25:1:28:3 | **kwargs | type tracker without call steps | type_tracker.rb:25:1:28:3 | **kwargs |
|
||||
| type_tracker.rb:25:1:28:3 | keyword | type tracker without call steps | type_tracker.rb:25:1:28:3 | keyword |
|
||||
| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:25:1:28:3 | return return in keyword |
|
||||
| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:30:1:30:21 | call to keyword |
|
||||
| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:31:1:31:21 | call to keyword |
|
||||
| type_tracker.rb:25:1:28:3 | return return in keyword | type tracker without call steps | type_tracker.rb:32:1:32:27 | call to keyword |
|
||||
| type_tracker.rb:25:1:28:3 | self (keyword) | type tracker without call steps | type_tracker.rb:25:1:28:3 | self (keyword) |
|
||||
| type_tracker.rb:25:1:28:3 | self in keyword | type tracker with call steps | type_tracker.rb:25:1:28:3 | self (keyword) |
|
||||
| type_tracker.rb:25:1:28:3 | self in keyword | type tracker without call steps | type_tracker.rb:25:1:28:3 | self in keyword |
|
||||
| type_tracker.rb:25:13:25:14 | p1 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:25:13:25:14 | p1 | type tracker without call steps | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:25:13:25:14 | p1 | type tracker without call steps | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:25:13:25:14 | p1 | type tracker without call steps | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:25:18:25:19 | p2 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:25:18:25:19 | p2 | type tracker without call steps | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:25:18:25:19 | p2 | type tracker without call steps | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:25:18:25:19 | p2 | type tracker without call steps | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:26:5:26:11 | call to puts | type tracker without call steps | type_tracker.rb:26:5:26:11 | call to puts |
|
||||
| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:25:1:28:3 | return return in keyword |
|
||||
| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:27:5:27:11 | call to puts |
|
||||
| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:30:1:30:21 | call to keyword |
|
||||
| type_tracker.rb:27:5:27:11 | call to puts | type tracker without call steps | type_tracker.rb:31:1:31:21 | call to keyword |
|
||||
@@ -134,14 +77,12 @@ track
|
||||
| type_tracker.rb:30:9:30:10 | :p1 | type tracker without call steps | type_tracker.rb:30:9:30:10 | :p1 |
|
||||
| type_tracker.rb:30:9:30:13 | Pair | type tracker without call steps | type_tracker.rb:30:9:30:13 | Pair |
|
||||
| type_tracker.rb:30:13:30:13 | 3 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:30:13:30:13 | 3 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:30:13:30:13 | 3 | type tracker with call steps with content element :p1 | type_tracker.rb:25:1:28:3 | **kwargs |
|
||||
| type_tracker.rb:30:13:30:13 | 3 | type tracker without call steps | type_tracker.rb:30:13:30:13 | 3 |
|
||||
| type_tracker.rb:30:13:30:13 | 3 | type tracker without call steps with content element :p1 | type_tracker.rb:30:1:30:21 | ** |
|
||||
| type_tracker.rb:30:16:30:17 | :p2 | type tracker without call steps | type_tracker.rb:30:16:30:17 | :p2 |
|
||||
| type_tracker.rb:30:16:30:20 | Pair | type tracker without call steps | type_tracker.rb:30:16:30:20 | Pair |
|
||||
| type_tracker.rb:30:20:30:20 | 4 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:30:20:30:20 | 4 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:30:20:30:20 | 4 | type tracker with call steps with content element :p2 | type_tracker.rb:25:1:28:3 | **kwargs |
|
||||
| type_tracker.rb:30:20:30:20 | 4 | type tracker without call steps | type_tracker.rb:30:20:30:20 | 4 |
|
||||
| type_tracker.rb:30:20:30:20 | 4 | type tracker without call steps with content element :p2 | type_tracker.rb:30:1:30:21 | ** |
|
||||
@@ -151,14 +92,12 @@ track
|
||||
| type_tracker.rb:31:9:31:10 | :p2 | type tracker without call steps | type_tracker.rb:31:9:31:10 | :p2 |
|
||||
| type_tracker.rb:31:9:31:13 | Pair | type tracker without call steps | type_tracker.rb:31:9:31:13 | Pair |
|
||||
| type_tracker.rb:31:13:31:13 | 5 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:31:13:31:13 | 5 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:31:13:31:13 | 5 | type tracker with call steps with content element :p2 | type_tracker.rb:25:1:28:3 | **kwargs |
|
||||
| type_tracker.rb:31:13:31:13 | 5 | type tracker without call steps | type_tracker.rb:31:13:31:13 | 5 |
|
||||
| type_tracker.rb:31:13:31:13 | 5 | type tracker without call steps with content element :p2 | type_tracker.rb:31:1:31:21 | ** |
|
||||
| type_tracker.rb:31:16:31:17 | :p1 | type tracker without call steps | type_tracker.rb:31:16:31:17 | :p1 |
|
||||
| type_tracker.rb:31:16:31:20 | Pair | type tracker without call steps | type_tracker.rb:31:16:31:20 | Pair |
|
||||
| type_tracker.rb:31:20:31:20 | 6 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:31:20:31:20 | 6 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:31:20:31:20 | 6 | type tracker with call steps with content element :p1 | type_tracker.rb:25:1:28:3 | **kwargs |
|
||||
| type_tracker.rb:31:20:31:20 | 6 | type tracker without call steps | type_tracker.rb:31:20:31:20 | 6 |
|
||||
| type_tracker.rb:31:20:31:20 | 6 | type tracker without call steps with content element :p1 | type_tracker.rb:31:1:31:21 | ** |
|
||||
@@ -168,68 +107,33 @@ track
|
||||
| type_tracker.rb:32:9:32:11 | :p2 | type tracker without call steps | type_tracker.rb:32:9:32:11 | :p2 |
|
||||
| type_tracker.rb:32:9:32:16 | Pair | type tracker without call steps | type_tracker.rb:32:9:32:16 | Pair |
|
||||
| type_tracker.rb:32:16:32:16 | 7 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:32:16:32:16 | 7 | type tracker with call steps | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:32:16:32:16 | 7 | type tracker with call steps with content element :p2 | type_tracker.rb:25:1:28:3 | **kwargs |
|
||||
| type_tracker.rb:32:16:32:16 | 7 | type tracker without call steps | type_tracker.rb:32:16:32:16 | 7 |
|
||||
| type_tracker.rb:32:16:32:16 | 7 | type tracker without call steps with content element :p2 | type_tracker.rb:32:1:32:27 | ** |
|
||||
| type_tracker.rb:32:19:32:21 | :p1 | type tracker without call steps | type_tracker.rb:32:19:32:21 | :p1 |
|
||||
| type_tracker.rb:32:19:32:26 | Pair | type tracker without call steps | type_tracker.rb:32:19:32:26 | Pair |
|
||||
| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps with content element :p1 | type_tracker.rb:25:1:28:3 | **kwargs |
|
||||
| type_tracker.rb:32:26:32:26 | 8 | type tracker without call steps | type_tracker.rb:32:26:32:26 | 8 |
|
||||
| type_tracker.rb:32:26:32:26 | 8 | type tracker without call steps with content element :p1 | type_tracker.rb:32:1:32:27 | ** |
|
||||
| type_tracker.rb:34:1:53:3 | &block | type tracker without call steps | type_tracker.rb:34:1:53:3 | &block |
|
||||
| type_tracker.rb:34:1:53:3 | return return in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:34:1:53:3 | self in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | self in throughArray |
|
||||
| type_tracker.rb:34:1:53:3 | throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | throughArray |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:34:18:34:20 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:36:5:36:10 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:40:5:40:12 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:44:5:44:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:38:13:38:25 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:44:5:44:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:38:13:38:25 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:38:13:38:25 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:44:5:44:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:44:5:44:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
|
||||
| type_tracker.rb:34:23:34:23 | y | type tracker with call steps | type_tracker.rb:34:23:34:23 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y |
|
||||
| type_tracker.rb:34:26:34:26 | z | type tracker with call steps | type_tracker.rb:34:26:34:26 | z |
|
||||
| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z |
|
||||
| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z |
|
||||
| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z |
|
||||
| type_tracker.rb:35:5:35:7 | tmp | type tracker without call steps | type_tracker.rb:35:5:35:7 | tmp |
|
||||
@@ -315,46 +219,33 @@ track
|
||||
| type_tracker.rb:50:5:50:10 | array4 | type tracker without call steps | type_tracker.rb:50:5:50:10 | array4 |
|
||||
| type_tracker.rb:50:14:50:26 | Array | type tracker without call steps | type_tracker.rb:50:14:50:26 | Array |
|
||||
| type_tracker.rb:50:14:50:26 | call to [] | type tracker without call steps | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:50:15:50:15 | 1 |
|
||||
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:50:17:50:17 | 2 |
|
||||
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:50:19:50:19 | 3 |
|
||||
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:50:21:50:21 | 4 |
|
||||
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:50:23:50:23 | 5 |
|
||||
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:50:25:50:25 | 6 |
|
||||
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:51:5:51:10 | [post] array4 | type tracker without call steps | type_tracker.rb:51:5:51:10 | [post] array4 |
|
||||
| type_tracker.rb:51:5:51:13 | call to []= | type tracker without call steps | type_tracker.rb:51:5:51:13 | call to []= |
|
||||
| type_tracker.rb:51:17:51:19 | __synth__0 | type tracker without call steps | type_tracker.rb:51:17:51:19 | __synth__0 |
|
||||
| type_tracker.rb:52:5:52:13 | ...[...] | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:52:5:52:13 | ...[...] | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
trackEnd
|
||||
| type_tracker.rb:1:1:10:3 | self (Container) | type_tracker.rb:1:1:10:3 | self (Container) |
|
||||
@@ -373,15 +264,6 @@ trackEnd
|
||||
| type_tracker.rb:1:1:53:4 | self (type_tracker.rb) | type_tracker.rb:32:1:32:27 | self |
|
||||
| type_tracker.rb:2:5:5:7 | &block | type_tracker.rb:2:5:5:7 | &block |
|
||||
| type_tracker.rb:2:5:5:7 | field= | type_tracker.rb:2:5:5:7 | field= |
|
||||
| type_tracker.rb:2:5:5:7 | return return in field= | type_tracker.rb:2:5:5:7 | return return in field= |
|
||||
| type_tracker.rb:2:5:5:7 | return return in field= | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:2:5:5:7 | self (field=) |
|
||||
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:3:9:3:23 | self |
|
||||
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:3:14:3:17 | self |
|
||||
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:4:9:4:14 | self |
|
||||
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:8:9:8:14 | self |
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:2:5:5:7 | self (field=) |
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:2:5:5:7 | self in field= |
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:3:9:3:23 | self |
|
||||
@@ -390,25 +272,14 @@ trackEnd
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:8:9:8:14 | self |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:5:5:7 | return return in field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:5:5:7 | return return in field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:7:5:9:7 | return return in field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:7:5:9:7 | return return in field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:14:5:14:13 | call to field= |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:3:9:3:23 | call to puts | type_tracker.rb:3:9:3:23 | call to puts |
|
||||
| type_tracker.rb:3:14:3:23 | call to field | type_tracker.rb:3:14:3:23 | call to field |
|
||||
@@ -416,23 +287,14 @@ trackEnd
|
||||
| type_tracker.rb:7:5:9:7 | &block | type_tracker.rb:7:5:9:7 | &block |
|
||||
| type_tracker.rb:7:5:9:7 | field | type_tracker.rb:1:1:10:3 | Container |
|
||||
| type_tracker.rb:7:5:9:7 | field | type_tracker.rb:7:5:9:7 | field |
|
||||
| type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:7:5:9:7 | return return in field |
|
||||
| type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:7:5:9:7 | self (field) | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:7:5:9:7 | self (field) | type_tracker.rb:8:9:8:14 | self |
|
||||
| type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:7:5:9:7 | self (field) |
|
||||
| type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:7:5:9:7 | self in field |
|
||||
| type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:8:9:8:14 | self |
|
||||
| type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:3:14:3:23 | call to field |
|
||||
| type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:7:5:9:7 | return return in field |
|
||||
| type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:8:9:8:14 | @field |
|
||||
| type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:12:1:16:3 | &block | type_tracker.rb:12:1:16:3 | &block |
|
||||
| type_tracker.rb:12:1:16:3 | m | type_tracker.rb:12:1:16:3 | m |
|
||||
| type_tracker.rb:12:1:16:3 | return return in m | type_tracker.rb:12:1:16:3 | return return in m |
|
||||
| type_tracker.rb:12:1:16:3 | self (m) | type_tracker.rb:12:1:16:3 | self (m) |
|
||||
| type_tracker.rb:12:1:16:3 | self (m) | type_tracker.rb:15:5:15:18 | self |
|
||||
| type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:12:1:16:3 | self (m) |
|
||||
| type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:12:1:16:3 | self in m |
|
||||
| type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:15:5:15:18 | self |
|
||||
@@ -470,16 +332,10 @@ trackEnd
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:17:14:23 | __synth__0 |
|
||||
| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:14:17:14:23 | __synth__0 | type_tracker.rb:14:17:14:23 | __synth__0 |
|
||||
| type_tracker.rb:15:5:15:18 | call to puts | type_tracker.rb:12:1:16:3 | return return in m |
|
||||
| type_tracker.rb:15:5:15:18 | call to puts | type_tracker.rb:15:5:15:18 | call to puts |
|
||||
| type_tracker.rb:15:10:15:18 | call to field | type_tracker.rb:15:10:15:18 | call to field |
|
||||
| type_tracker.rb:18:1:21:3 | &block | type_tracker.rb:18:1:21:3 | &block |
|
||||
| type_tracker.rb:18:1:21:3 | positional | type_tracker.rb:18:1:21:3 | positional |
|
||||
| type_tracker.rb:18:1:21:3 | return return in positional | type_tracker.rb:18:1:21:3 | return return in positional |
|
||||
| type_tracker.rb:18:1:21:3 | return return in positional | type_tracker.rb:23:1:23:16 | call to positional |
|
||||
| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:18:1:21:3 | self (positional) |
|
||||
| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:19:5:19:11 | self |
|
||||
| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:20:5:20:11 | self |
|
||||
| type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:18:1:21:3 | self (positional) |
|
||||
| type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:18:1:21:3 | self in positional |
|
||||
| type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:19:5:19:11 | self |
|
||||
@@ -487,17 +343,12 @@ trackEnd
|
||||
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 |
|
||||
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 |
|
||||
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 |
|
||||
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 |
|
||||
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:19:10:19:11 | p1 |
|
||||
| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:19:10:19:11 | p1 |
|
||||
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 |
|
||||
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 |
|
||||
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 |
|
||||
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 |
|
||||
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:20:10:20:11 | p2 |
|
||||
| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:20:10:20:11 | p2 |
|
||||
| type_tracker.rb:19:5:19:11 | call to puts | type_tracker.rb:19:5:19:11 | call to puts |
|
||||
| type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:18:1:21:3 | return return in positional |
|
||||
| type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:20:5:20:11 | call to puts |
|
||||
| type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:23:1:23:16 | call to positional |
|
||||
| type_tracker.rb:23:1:23:16 | call to positional | type_tracker.rb:23:1:23:16 | call to positional |
|
||||
@@ -512,13 +363,6 @@ trackEnd
|
||||
| type_tracker.rb:25:1:28:3 | &block | type_tracker.rb:25:1:28:3 | &block |
|
||||
| type_tracker.rb:25:1:28:3 | **kwargs | type_tracker.rb:25:1:28:3 | **kwargs |
|
||||
| type_tracker.rb:25:1:28:3 | keyword | type_tracker.rb:25:1:28:3 | keyword |
|
||||
| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:25:1:28:3 | return return in keyword |
|
||||
| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:30:1:30:21 | call to keyword |
|
||||
| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:31:1:31:21 | call to keyword |
|
||||
| type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:32:1:32:27 | call to keyword |
|
||||
| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:25:1:28:3 | self (keyword) |
|
||||
| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:26:5:26:11 | self |
|
||||
| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:27:5:27:11 | self |
|
||||
| type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:25:1:28:3 | self (keyword) |
|
||||
| type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:25:1:28:3 | self in keyword |
|
||||
| type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:26:5:26:11 | self |
|
||||
@@ -526,17 +370,12 @@ trackEnd
|
||||
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 |
|
||||
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:26:10:26:11 | p1 |
|
||||
| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:26:10:26:11 | p1 |
|
||||
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 |
|
||||
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:27:10:27:11 | p2 |
|
||||
| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:27:10:27:11 | p2 |
|
||||
| type_tracker.rb:26:5:26:11 | call to puts | type_tracker.rb:26:5:26:11 | call to puts |
|
||||
| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:25:1:28:3 | return return in keyword |
|
||||
| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:27:5:27:11 | call to puts |
|
||||
| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:30:1:30:21 | call to keyword |
|
||||
| type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:31:1:31:21 | call to keyword |
|
||||
@@ -587,80 +426,45 @@ trackEnd
|
||||
| type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:26:10:26:11 | p1 |
|
||||
| type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:32:26:32:26 | 8 |
|
||||
| type_tracker.rb:34:1:53:3 | &block | type_tracker.rb:34:1:53:3 | &block |
|
||||
| type_tracker.rb:34:1:53:3 | return return in throughArray | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:34:1:53:3 | self in throughArray | type_tracker.rb:34:1:53:3 | self in throughArray |
|
||||
| type_tracker.rb:34:1:53:3 | throughArray | type_tracker.rb:34:1:53:3 | throughArray |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:12 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:12 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:18 | ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:18 | ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:40:5:40:12 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:40:5:40:12 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:13 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:13 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:19 | ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:19 | ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:13 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:13 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:19 | ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:19 | ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:13 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:13 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:19 | ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:19 | ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | __synth__0 |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | obj |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:51:12:51:12 | y |
|
||||
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:51:12:51:12 | y |
|
||||
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
|
||||
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
|
||||
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
|
||||
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
|
||||
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:52:12:52:12 | z |
|
||||
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:52:12:52:12 | z |
|
||||
| type_tracker.rb:35:5:35:7 | tmp | type_tracker.rb:35:5:35:7 | tmp |
|
||||
| type_tracker.rb:35:11:35:15 | Array | type_tracker.rb:35:11:35:15 | Array |
|
||||
@@ -743,29 +547,22 @@ trackEnd
|
||||
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:50:14:50:26 | call to [] |
|
||||
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:51:5:51:10 | array4 |
|
||||
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:52:5:52:10 | array4 |
|
||||
| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:50:15:50:15 | 1 |
|
||||
| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:50:17:50:17 | 2 |
|
||||
| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:50:19:50:19 | 3 |
|
||||
| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:50:21:50:21 | 4 |
|
||||
| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:50:23:50:23 | 5 |
|
||||
| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:50:25:50:25 | 6 |
|
||||
| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
| type_tracker.rb:51:5:51:10 | [post] array4 | type_tracker.rb:51:5:51:10 | [post] array4 |
|
||||
| type_tracker.rb:51:5:51:10 | [post] array4 | type_tracker.rb:52:5:52:10 | array4 |
|
||||
| type_tracker.rb:51:5:51:13 | call to []= | type_tracker.rb:51:5:51:13 | call to []= |
|
||||
| type_tracker.rb:51:17:51:19 | __synth__0 | type_tracker.rb:51:17:51:19 | __synth__0 |
|
||||
| type_tracker.rb:52:5:52:13 | ...[...] | type_tracker.rb:34:1:53:3 | return return in throughArray |
|
||||
| type_tracker.rb:52:5:52:13 | ...[...] | type_tracker.rb:52:5:52:13 | ...[...] |
|
||||
forwardButNoBackwardFlow
|
||||
backwardButNoForwardFlow
|
||||
|
||||
@@ -36,8 +36,8 @@ actionDispatchRoutes
|
||||
actionDispatchControllerMethods
|
||||
| app/config/routes.rb:2:3:8:5 | call to resources | app/controllers/posts_controller.rb:2:3:3:5 | index |
|
||||
| app/config/routes.rb:2:3:8:5 | call to resources | app/controllers/posts_controller.rb:5:3:6:5 | show |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:2:3:36:5 | index |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:38:3:39:5 | show |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:2:3:39:5 | index |
|
||||
| app/config/routes.rb:3:5:6:7 | call to resources | app/controllers/comments_controller.rb:41:3:42:5 | show |
|
||||
| app/config/routes.rb:7:5:7:37 | call to post | app/controllers/posts_controller.rb:8:3:9:5 | upvote |
|
||||
| app/config/routes.rb:27:3:27:48 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
| app/config/routes.rb:28:3:28:50 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
|
||||
@@ -66,3 +66,17 @@ mimeTypeMatchRegExpInterpretations
|
||||
| mime_type.rb:12:7:12:15 | "foo/bar" |
|
||||
| mime_type.rb:13:11:13:11 | s |
|
||||
| mime_type.rb:14:7:14:7 | s |
|
||||
requestInputAccesses
|
||||
| app/controllers/comments_controller.rb:3:5:3:18 | call to params |
|
||||
| app/controllers/comments_controller.rb:4:5:4:22 | call to parameters |
|
||||
| app/controllers/comments_controller.rb:5:5:5:15 | call to GET |
|
||||
| app/controllers/comments_controller.rb:6:5:6:16 | call to POST |
|
||||
| app/controllers/comments_controller.rb:7:5:7:28 | call to query_parameters |
|
||||
| app/controllers/comments_controller.rb:8:5:8:30 | call to request_parameters |
|
||||
| app/controllers/comments_controller.rb:9:5:9:31 | call to filtered_parameters |
|
||||
| app/controllers/comments_controller.rb:38:5:38:14 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies |
|
||||
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params |
|
||||
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params |
|
||||
|
||||
@@ -2,6 +2,7 @@ private import codeql.ruby.AST
|
||||
private import codeql.ruby.frameworks.ActionDispatch
|
||||
private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.frameworks.data.ModelsAsData
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.Regexp as RE
|
||||
@@ -36,3 +37,5 @@ query predicate mimeTypeMatchRegExpInterpretations(
|
||||
) {
|
||||
any()
|
||||
}
|
||||
|
||||
query predicate requestInputAccesses(Http::Server::RequestInputAccess a) { any() }
|
||||
|
||||
@@ -33,6 +33,9 @@ class CommentsController < ApplicationController
|
||||
response.last_modified = Date.yesterday
|
||||
response.weak_etag = "value"
|
||||
response.strong_etag = "value"
|
||||
|
||||
req = ActionDispatch::Request.new(request.env)
|
||||
req.params
|
||||
end
|
||||
|
||||
def show
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
| Mysql2.rb:10:16:10:48 | call to query | Mysql2.rb:10:27:10:47 | "SELECT * FROM users" |
|
||||
| Mysql2.rb:13:16:13:73 | call to query | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." |
|
||||
| Mysql2.rb:17:16:17:76 | call to query | Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." |
|
||||
| Mysql2.rb:21:16:21:57 | call to execute | Mysql2.rb:20:31:20:82 | "SELECT * FROM users WHERE id ..." |
|
||||
| Mysql2.rb:25:16:25:60 | call to execute | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." |
|
||||
5
ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.ql
Normal file
5
ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.ql
Normal file
@@ -0,0 +1,5 @@
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.frameworks.Mysql2
|
||||
|
||||
query predicate mysql2SqlExecution(SqlExecution e, DataFlow::Node sql) { sql = e.getSql() }
|
||||
30
ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.rb
Normal file
30
ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
class UsersController < ActionController::Base
|
||||
def mysql2_handler(event:, context:)
|
||||
name = params[:user_name]
|
||||
|
||||
conn = Mysql2::Client.new(
|
||||
host: "127.0.0.1",
|
||||
username: "root"
|
||||
)
|
||||
# GOOD: SQL statement is not constructed from user input
|
||||
results1 = conn.query("SELECT * FROM users")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
results2 = conn.query("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# GOOD: user input is escaped
|
||||
escaped = Mysql2::Client.escape(name)
|
||||
results3 = conn.query("SELECT * FROM users WHERE username='#{escaped}'")
|
||||
|
||||
# GOOD: user input is escaped
|
||||
statement1 = conn.prepare("SELECT * FROM users WHERE id >= ? AND username = ?")
|
||||
results4 = statement1.execute(1, name, :as => :array)
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
statement2 = conn.prepare("SELECT * FROM users WHERE username='#{name}' AND password = ?")
|
||||
results4 = statement2.execute("password", :as => :array)
|
||||
|
||||
# NOT EXECUTED
|
||||
statement3 = conn.prepare("SELECT * FROM users WHERE username = ?")
|
||||
end
|
||||
end
|
||||
23
ruby/ql/test/library-tests/frameworks/sequel/Sequel.expected
Normal file
23
ruby/ql/test/library-tests/frameworks/sequel/Sequel.expected
Normal file
@@ -0,0 +1,23 @@
|
||||
sequelSqlConstruction
|
||||
| sequel.rb:62:29:62:49 | call to cast | sequel.rb:62:45:62:48 | name |
|
||||
| sequel.rb:65:29:65:49 | call to function | sequel.rb:65:45:65:48 | name |
|
||||
sequelSqlExecution
|
||||
| sequel.rb:9:9:9:60 | ...[...] | sequel.rb:9:14:9:59 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:12:9:12:64 | call to run | sequel.rb:12:18:12:63 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:15:9:17:11 | call to fetch | sequel.rb:15:20:15:65 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:20:9:20:65 | ...[...] | sequel.rb:20:14:20:64 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:23:9:23:65 | call to execute | sequel.rb:23:22:23:65 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:26:9:26:71 | call to execute_ddl | sequel.rb:26:26:26:71 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:29:9:29:71 | call to execute_dui | sequel.rb:29:26:29:71 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:32:9:32:74 | call to execute_insert | sequel.rb:32:29:32:74 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:35:9:35:62 | ... << ... | sequel.rb:35:17:35:62 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:38:9:38:79 | call to fetch_rows | sequel.rb:38:25:38:70 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:41:9:41:81 | call to with_sql_all | sequel.rb:41:35:41:80 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:44:9:44:84 | call to with_sql_delete | sequel.rb:44:38:44:83 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:47:9:47:90 | call to with_sql_each | sequel.rb:47:36:47:81 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:50:9:50:83 | call to with_sql_first | sequel.rb:50:37:50:82 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:53:9:53:84 | call to with_sql_insert | sequel.rb:53:38:53:83 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:56:9:56:90 | call to with_sql_single_value | sequel.rb:56:44:56:89 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:59:9:59:84 | call to with_sql_update | sequel.rb:59:38:59:83 | "SELECT * FROM users WHERE use..." |
|
||||
| sequel.rb:62:9:62:20 | ...[...] | sequel.rb:62:14:62:19 | :table |
|
||||
| sequel.rb:65:9:65:20 | ...[...] | sequel.rb:65:14:65:19 | :table |
|
||||
7
ruby/ql/test/library-tests/frameworks/sequel/Sequel.ql
Normal file
7
ruby/ql/test/library-tests/frameworks/sequel/Sequel.ql
Normal file
@@ -0,0 +1,7 @@
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.frameworks.Sequel
|
||||
|
||||
query predicate sequelSqlConstruction(SqlConstruction c, DataFlow::Node sql) { sql = c.getSql() }
|
||||
|
||||
query predicate sequelSqlExecution(SqlExecution e, DataFlow::Node sql) { sql = e.getSql() }
|
||||
67
ruby/ql/test/library-tests/frameworks/sequel/sequel.rb
Normal file
67
ruby/ql/test/library-tests/frameworks/sequel/sequel.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
require 'sequel'
|
||||
|
||||
class UsersController < ActionController::Base
|
||||
def sequel_handler(event:, context:)
|
||||
name = params[:name]
|
||||
conn = Sequel.sqlite("sqlite://example.db")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn["SELECT * FROM users WHERE username='#{name}'"]
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.run("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.fetch("SELECT * FROM users WHERE username='#{name}'") do |row|
|
||||
puts row[:name]
|
||||
end
|
||||
|
||||
# GOOD: SQL statement is not constructed from user input
|
||||
conn["SELECT * FROM users WHERE username='im_not_input'"]
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.execute "SELECT * FROM users WHERE username=#{name}"
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.execute_ddl "SELECT * FROM users WHERE username='#{name}'"
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.execute_dui "SELECT * FROM users WHERE username='#{name}'"
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.execute_insert "SELECT * FROM users WHERE username='#{name}'"
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn << "SELECT * FROM users WHERE username='#{name}'"
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.fetch_rows("SELECT * FROM users WHERE username='#{name}'"){|row| }
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_all("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_delete("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_each("SELECT * FROM users WHERE username='#{name}'"){|row| }
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_first("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_insert("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_single_value("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn.dataset.with_sql_update("SELECT * FROM users WHERE username='#{name}'")
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn[:table].select(Sequel.cast(:a, name))
|
||||
|
||||
# BAD: SQL statement constructed from user input
|
||||
conn[:table].select(Sequel.function(name))
|
||||
end
|
||||
end
|
||||
@@ -40,6 +40,9 @@ edges
|
||||
| PolynomialReDoS.rb:70:12:70:24 | ...[...] | PolynomialReDoS.rb:70:5:70:8 | name |
|
||||
| PolynomialReDoS.rb:73:32:73:35 | name | PolynomialReDoS.rb:76:35:76:39 | input |
|
||||
| PolynomialReDoS.rb:76:35:76:39 | input | PolynomialReDoS.rb:77:5:77:9 | input |
|
||||
| PolynomialReDoS.rb:103:5:103:8 | name | PolynomialReDoS.rb:105:5:105:8 | name |
|
||||
| PolynomialReDoS.rb:103:12:103:17 | call to params | PolynomialReDoS.rb:103:12:103:24 | ...[...] |
|
||||
| PolynomialReDoS.rb:103:12:103:24 | ...[...] | PolynomialReDoS.rb:103:5:103:8 | name |
|
||||
| lib/index.rb:2:11:2:11 | x | lib/index.rb:4:13:4:13 | x |
|
||||
| lib/index.rb:8:13:8:13 | x | lib/index.rb:9:15:9:15 | x |
|
||||
| lib/index.rb:8:13:8:13 | x | lib/index.rb:11:16:11:16 | x |
|
||||
@@ -91,6 +94,10 @@ nodes
|
||||
| PolynomialReDoS.rb:73:32:73:35 | name | semmle.label | name |
|
||||
| PolynomialReDoS.rb:76:35:76:39 | input | semmle.label | input |
|
||||
| PolynomialReDoS.rb:77:5:77:9 | input | semmle.label | input |
|
||||
| PolynomialReDoS.rb:103:5:103:8 | name | semmle.label | name |
|
||||
| PolynomialReDoS.rb:103:12:103:17 | call to params | semmle.label | call to params |
|
||||
| PolynomialReDoS.rb:103:12:103:24 | ...[...] | semmle.label | ...[...] |
|
||||
| PolynomialReDoS.rb:105:5:105:8 | name | semmle.label | name |
|
||||
| lib/index.rb:2:11:2:11 | x | semmle.label | x |
|
||||
| lib/index.rb:4:13:4:13 | x | semmle.label | x |
|
||||
| lib/index.rb:8:13:8:13 | x | semmle.label | x |
|
||||
@@ -121,6 +128,8 @@ subpaths
|
||||
| PolynomialReDoS.rb:62:5:62:22 | call to gsub | PolynomialReDoS.rb:54:12:54:17 | call to params | PolynomialReDoS.rb:62:5:62:9 | input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:56:31:56:33 | \\s+ | regular expression | PolynomialReDoS.rb:54:12:54:17 | call to params | user-provided value |
|
||||
| PolynomialReDoS.rb:66:5:66:34 | call to match? | PolynomialReDoS.rb:54:12:54:17 | call to params | PolynomialReDoS.rb:66:5:66:9 | input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:58:30:58:32 | \\s+ | regular expression | PolynomialReDoS.rb:54:12:54:17 | call to params | user-provided value |
|
||||
| PolynomialReDoS.rb:77:5:77:22 | call to gsub | PolynomialReDoS.rb:70:12:70:17 | call to params | PolynomialReDoS.rb:77:5:77:9 | input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | PolynomialReDoS.rb:72:28:72:30 | \\s+ | regular expression | PolynomialReDoS.rb:70:12:70:17 | call to params | user-provided value |
|
||||
| PolynomialReDoS.rb:105:5:105:23 | ... =~ ... | PolynomialReDoS.rb:103:12:103:17 | call to params | PolynomialReDoS.rb:105:5:105:8 | name | This $@ that depends on a $@ may run slow on strings starting with '''' and with many repetitions of ' '. | PolynomialReDoS.rb:100:397:100:399 | \\s* | regular expression | PolynomialReDoS.rb:103:12:103:17 | call to params | user-provided value |
|
||||
| PolynomialReDoS.rb:105:5:105:23 | ... =~ ... | PolynomialReDoS.rb:103:12:103:17 | call to params | PolynomialReDoS.rb:105:5:105:8 | name | This $@ that depends on a $@ may run slow on strings starting with '''' and with many repetitions of ' '. | PolynomialReDoS.rb:100:405:100:407 | \\s* | regular expression | PolynomialReDoS.rb:103:12:103:17 | call to params | user-provided value |
|
||||
| lib/index.rb:4:13:4:26 | call to match | lib/index.rb:2:11:2:11 | x | lib/index.rb:4:13:4:13 | x | This $@ that depends on a $@ may run slow on strings with many repetitions of 'a'. | lib/index.rb:4:22:4:23 | a+ | regular expression | lib/index.rb:2:11:2:11 | x | library input |
|
||||
| lib/index.rb:9:15:9:28 | call to match | lib/index.rb:8:13:8:13 | x | lib/index.rb:9:15:9:15 | x | This $@ that depends on a $@ may run slow on strings with many repetitions of 'a'. | lib/index.rb:9:24:9:25 | a+ | regular expression | lib/index.rb:8:13:8:13 | x | library input |
|
||||
| lib/index.rb:11:16:11:276 | call to match | lib/index.rb:8:13:8:13 | x | lib/index.rb:11:16:11:16 | x | This $@ that depends on a $@ may run slow on strings starting with 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC' and with many repetitions of 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC'. | lib/index.rb:11:271:11:272 | .* | regular expression | lib/index.rb:8:13:8:13 | x | library input |
|
||||
|
||||
@@ -76,4 +76,32 @@ class FooController < ActionController::Base
|
||||
def re_compile_indirect_2 (reg, input)
|
||||
input.gsub reg, '' # NOT GOOD
|
||||
end
|
||||
|
||||
# See https://github.com/dependabot/dependabot-core/blob/37dc1767fde9b7184020763f4d0c1434f93d11d6/python/lib/dependabot/python/requirement_parser.rb#L6-L25
|
||||
NAME = /[a-zA-Z0-9](?:[a-zA-Z0-9\-_\.]*[a-zA-Z0-9])?/
|
||||
EXTRA = /[a-zA-Z0-9\-_\.]+/
|
||||
COMPARISON = /===|==|>=|<=|<|>|~=|!=/
|
||||
VERSION = /([1-9][0-9]*!)?[0-9]+[a-zA-Z0-9\-_.*]*(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?/
|
||||
|
||||
REQUIREMENT = /(?<comparison>#{COMPARISON})\s*\\?\s*(?<version>#{VERSION})/
|
||||
HASH = /--hash=(?<algorithm>.*?):(?<hash>.*?)(?=\s|\\|$)/
|
||||
REQUIREMENTS = /#{REQUIREMENT}(\s*,\s*\\?\s*#{REQUIREMENT})*/
|
||||
HASHES = /#{HASH}(\s*\\?\s*#{HASH})*/
|
||||
MARKER_OP = /\s*(#{COMPARISON}|(\s*in)|(\s*not\s*in))/
|
||||
PYTHON_STR_C = %r{[a-zA-Z0-9\s\(\)\.\{\}\-_\*#:;/\?\[\]!~`@\$%\^&=\+\|<>]}
|
||||
PYTHON_STR = /('(#{PYTHON_STR_C}|")*'|"(#{PYTHON_STR_C}|')*")/
|
||||
ENV_VAR =
|
||||
/python_version|python_full_version|os_name|sys_platform|
|
||||
platform_release|platform_system|platform_version|platform_machine|
|
||||
platform_python_implementation|implementation_name|
|
||||
implementation_version/
|
||||
MARKER_VAR = /\s*(#{ENV_VAR}|#{PYTHON_STR})/
|
||||
MARKER_EXPR_ONE = /#{MARKER_VAR}#{MARKER_OP}#{MARKER_VAR}/
|
||||
MARKER_EXPR = /(#{MARKER_EXPR_ONE}|\(\s*|\s*\)|\s+and\s+|\s+or\s+)+/
|
||||
|
||||
def use_marker_expr
|
||||
name = params[:name] # source
|
||||
|
||||
name =~ MARKER_EXPR
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user