Merge branch 'main' into ruby-more-flow

This commit is contained in:
Arthur Baars
2022-12-05 11:13:46 +01:00
committed by GitHub
1614 changed files with 119155 additions and 106743 deletions

View File

@@ -3,7 +3,15 @@ description: Builds the Ruby CodeQL pack
runs:
using: composite
steps:
- uses: actions/cache@v3
- name: Cache entire extractor
id: cache-extractor
uses: actions/cache@v3
with:
path: ruby/extractor-pack
key: ${{ runner.os }}-extractor-${{ hashFiles('ruby/rust-toolchain.toml', 'ruby/**/Cargo.lock') }}-${{ hashFiles('ruby/**/*.rs') }}-${{ hashFiles('ruby/codeql-extractor.yml', 'ruby/downgrades', 'ruby/tools', 'ruby/ql/lib/ruby.dbscheme', 'ruby/ql/lib/ruby.dbscheme.stats') }}
- name: Cache cargo
uses: actions/cache@v3
if: steps.cache-extractor.outputs.cache-hit != 'true'
with:
path: |
~/.cargo/registry
@@ -11,6 +19,7 @@ runs:
ruby/target
key: ${{ runner.os }}-ruby-qltest-cargo-${{ hashFiles('ruby/rust-toolchain.toml', 'ruby/**/Cargo.lock') }}
- name: Build Extractor
if: steps.cache-extractor.outputs.cache-hit != 'true'
shell: bash
run: scripts/create-extractor-pack.sh
working-directory: ruby

View File

@@ -9,4 +9,4 @@ ruby_rational_def.rel: run ruby_rational_def.qlo
ruby_call_method.rel: delete
ruby_complex_def.rel: delete
ruby_block_parameters_locals.rel: delete
ruby_call_operator: delete
ruby_call_operator.rel: delete

View File

@@ -81,6 +81,14 @@ pub enum Storage {
},
}
impl Storage {
pub fn is_column(&self) -> bool {
match self {
Storage::Column { .. } => true,
_ => false,
}
}
}
pub fn read_node_types(prefix: &str, node_types_path: &Path) -> std::io::Result<NodeTypeMap> {
let file = fs::File::open(node_types_path)?;
let node_types: Vec<NodeInfo> = serde_json::from_reader(file)?;
@@ -245,10 +253,11 @@ fn add_field(
}
};
let converted_types = convert_types(&field_info.types);
let type_info = if field_info
.types
.iter()
.all(|t| !t.named && token_kinds.contains(&convert_type(t)))
let type_info = if storage.is_column()
&& field_info
.types
.iter()
.all(|t| !t.named && token_kinds.contains(&convert_type(t)))
{
// All possible types for this field are reserved words. The db
// representation will be an `int` with a `case @foo.field = ...` to

View File

@@ -1,7 +1,8 @@
import codeql.ruby.dataflow.SSA
import codeql.ruby.dataflow.internal.SsaImpl::Consistency as Consistency
import codeql.ruby.dataflow.internal.SsaImpl
import Consistency
class MyRelevantDefinition extends Consistency::RelevantDefinition, Ssa::Definition {
class MyRelevantDefinition extends RelevantDefinition, Ssa::Definition {
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
@@ -9,10 +10,10 @@ class MyRelevantDefinition extends Consistency::RelevantDefinition, Ssa::Definit
}
}
query predicate nonUniqueDef = Consistency::nonUniqueDef/4;
query predicate readWithoutDef = Consistency::readWithoutDef/3;
query predicate deadDef = Consistency::deadDef/2;
query predicate notDominatedByDef = Consistency::notDominatedByDef/4;
class MyRelevantDefinitionExt extends RelevantDefinitionExt, DefinitionExt {
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}

View File

@@ -1,3 +1,17 @@
## 0.4.4
### Minor Analysis Improvements
* Data flow through the `ActiveSupport` extension `Enumerable#index_by` is now modeled.
* The `codeql.ruby.Concepts` library now has a `SqlConstruction` class, in addition to the existing `SqlExecution` class.
* Calls to `Arel.sql` are now modeled as instances of the new `SqlConstruction` concept.
* Arguments to RPC endpoints (public methods) on subclasses of `ActionCable::Channel::Base` are now recognized as sources of remote user input.
* Taint flow through the `ActiveSupport` extensions `Hash#reverse_merge` and `Hash:reverse_merge!`, and their aliases, is now modeled more generally, where previously it was only modeled in the context of `ActionController` parameters.
* Calls to `logger` in `ActiveSupport` actions are now recognised as logger instances.
* Calls to `send_data` in `ActiveSupport` actions are recognised as HTTP responses.
* Calls to `body_stream` in `ActiveSupport` actions are recognised as HTTP request accesses.
* The `ActiveSupport` extensions `Object#try` and `Object#try!` are now recognised as code executions.
## 0.4.3
### Minor Analysis Improvements

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* String literals and arrays of string literals in case expression patterns are now recognised as barrier guards.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* The `ActiveSupport` extensions `Object#try` and `Object#try!` are now recognised as code executions.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The ReDoS libraries in `codeql.ruby.security.regexp` has been moved to a shared pack inside the `shared/` folder, and the previous location has been deprecated.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Taint flow through the `ActiveSupport` extensions `Hash#reverse_merge` and `Hash:reverse_merge!`, and their aliases, is now modeled more generally, where previously it was only modeled in the context of `ActionController` parameters.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Arguments to RPC endpoints (public methods) on subclasses of `ActionCable::Channel::Base` are now recognized as sources of remote user input.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Taint flow is now tracked through many common JSON parsing and generation methods.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Data flow through the `ActiveSupport` extensions `Enumerable#index_with`, `Enumerable#pick`, `Enumerable#pluck` and `Enumerable#sole` are now modeled.

View File

@@ -0,0 +1,13 @@
## 0.4.4
### Minor Analysis Improvements
* Data flow through the `ActiveSupport` extension `Enumerable#index_by` is now modeled.
* The `codeql.ruby.Concepts` library now has a `SqlConstruction` class, in addition to the existing `SqlExecution` class.
* Calls to `Arel.sql` are now modeled as instances of the new `SqlConstruction` concept.
* Arguments to RPC endpoints (public methods) on subclasses of `ActionCable::Channel::Base` are now recognized as sources of remote user input.
* Taint flow through the `ActiveSupport` extensions `Hash#reverse_merge` and `Hash:reverse_merge!`, and their aliases, is now modeled more generally, where previously it was only modeled in the context of `ActionController` parameters.
* Calls to `logger` in `ActiveSupport` actions are now recognised as logger instances.
* Calls to `send_data` in `ActiveSupport` actions are recognised as HTTP responses.
* Calls to `body_stream` in `ActiveSupport` actions are recognised as HTTP request accesses.
* The `ActiveSupport` extensions `Object#try` and `Object#try!` are now recognised as code executions.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.4.3
lastReleaseVersion: 0.4.4

View File

@@ -6,7 +6,7 @@ import files.FileSystem
* A location as given by a file, a start line, a start column,
* an end line, and an end column.
*
* For more information about locations see [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
* For more information about locations see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
class Location extends @location {
/** Gets the file for this location. */
@@ -40,7 +40,7 @@ class Location extends @location {
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
* [Providing locations in CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn

View File

@@ -11,9 +11,47 @@ private import codeql.ruby.Frameworks
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.ApiGraphs
/**
* A data-flow node that constructs a SQL statement.
*
* Often, it is worthy of an alert if a SQL statement is constructed such that
* executing it would be a security risk.
*
* If it is important that the SQL statement is executed, use `SqlExecution`.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `SqlConstruction::Range` instead.
*/
class SqlConstruction extends DataFlow::Node instanceof SqlConstruction::Range {
/** Gets the argument that specifies the SQL statements to be constructed. */
DataFlow::Node getSql() { result = super.getSql() }
}
/** Provides a class for modeling new SQL execution APIs. */
module SqlConstruction {
/**
* A data-flow node that constructs a SQL statement.
*
* Often, it is worthy of an alert if a SQL statement is constructed such that
* executing it would be a security risk.
*
* If it is important that the SQL statement is executed, use `SqlExecution`.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `SqlConstruction` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the SQL statements to be constructed. */
abstract DataFlow::Node getSql();
}
}
/**
* A data-flow node that executes SQL statements.
*
* If the context of interest is such that merely constructing a SQL statement
* would be valuable to report, consider using `SqlConstruction`.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `SqlExecution::Range` instead.
*/
@@ -27,6 +65,9 @@ module SqlExecution {
/**
* A data-flow node that executes SQL statements.
*
* If the context of interest is such that merely constructing a SQL
* statement would be valuable to report, consider using `SqlConstruction`.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `SqlExecution` instead.
*/

View File

@@ -23,3 +23,5 @@ private import codeql.ruby.frameworks.HttpClients
private import codeql.ruby.frameworks.XmlParsing
private import codeql.ruby.frameworks.ActionDispatch
private import codeql.ruby.frameworks.PosixSpawn
private import codeql.ruby.frameworks.StringFormatters
private import codeql.ruby.frameworks.Json

View File

@@ -234,7 +234,7 @@ module ExprNodes {
override predicate relevantChild(AstNode n) { none() }
}
/** A control-flow node that wraps an `ArrayLiteral` AST expression. */
/** A control-flow node that wraps a `Literal` AST expression. */
class LiteralCfgNode extends ExprCfgNode {
override string getAPrimaryQlClass() { result = "LiteralCfgNode" }
@@ -432,8 +432,36 @@ module ExprNodes {
final ExprCfgNode getBody() { e.hasCfgChild(e.getBody(), this, result) }
}
private class WhenClauseChildMapping extends NonExprChildMapping, WhenClause {
override predicate relevantChild(AstNode e) { e = [this.getBody(), this.getAPattern()] }
// `when` clauses need special treatment, since they are neither pre-order
// nor post-order
private class WhenClauseChildMapping extends WhenClause {
predicate patternReachesBasicBlock(int i, CfgNode cfnPattern, BasicBlock bb) {
exists(Expr pattern |
pattern = this.getPattern(i) and
cfnPattern.getNode() = pattern and
bb.getANode() = cfnPattern
)
or
exists(BasicBlock mid |
this.patternReachesBasicBlock(i, cfnPattern, mid) and
bb = mid.getASuccessor() and
not mid.getANode().getNode() = this
)
}
predicate bodyReachesBasicBlock(CfgNode cfnBody, BasicBlock bb) {
exists(Stmt body |
body = this.getBody() and
cfnBody.getNode() = body and
bb.getANode() = cfnBody
)
or
exists(BasicBlock mid |
this.bodyReachesBasicBlock(cfnBody, mid) and
bb = mid.getAPredecessor() and
not mid.getANode().getNode() = this
)
}
}
/** A control-flow node that wraps a `WhenClause` AST expression. */
@@ -443,10 +471,16 @@ module ExprNodes {
override WhenClauseChildMapping e;
/** Gets the body of this `when`-clause. */
final ExprCfgNode getBody() { e.hasCfgChild(e.getBody(), this, result) }
final ExprCfgNode getBody() {
result.getNode() = desugar(e.getBody()) and
e.bodyReachesBasicBlock(result, this.getBasicBlock())
}
/** Gets the `i`th pattern this `when`-clause. */
final ExprCfgNode getPattern(int i) { e.hasCfgChild(e.getPattern(i), this, result) }
final ExprCfgNode getPattern(int i) {
result.getNode() = desugar(e.getPattern(i)) and
e.patternReachesBasicBlock(i, result, this.getBasicBlock())
}
}
/** A control-flow node that wraps a `CasePattern`. */
@@ -866,6 +900,19 @@ module ExprNodes {
final override RelationalOperation getExpr() { result = super.getExpr() }
}
private class SplatExprChildMapping extends OperationExprChildMapping, SplatExpr {
override predicate relevantChild(AstNode n) { n = this.getOperand() }
}
/** A control-flow node that wraps a `SplatExpr` AST expression. */
class SplatExprCfgNode extends UnaryOperationCfgNode {
override string getAPrimaryQlClass() { result = "SplatExprCfgNode" }
override SplatExprChildMapping e;
final override SplatExpr getExpr() { result = super.getExpr() }
}
/** A control-flow node that wraps an `ElementReference` AST expression. */
class ElementReferenceCfgNode extends MethodCallCfgNode {
override string getAPrimaryQlClass() { result = "ElementReferenceCfgNode" }

View File

@@ -211,8 +211,11 @@ private predicate inBooleanContext(AstNode n) {
or
exists(CaseExpr c, WhenClause w |
not exists(c.getValue()) and
c.getABranch() = w and
c.getABranch() = w
|
w.getPattern(_) = n
or
w = n
)
}
@@ -233,8 +236,11 @@ private predicate inMatchingContext(AstNode n) {
or
exists(CaseExpr c, WhenClause w |
exists(c.getValue()) and
c.getABranch() = w and
c.getABranch() = w
|
w.getPattern(_) = n
or
w = n
)
or
n instanceof CasePattern

View File

@@ -400,8 +400,9 @@ module Trees {
c instanceof SimpleCompletion
or
exists(int i, WhenTree branch | branch = this.getBranch(i) |
last(branch.getLastPattern(), pred, c) and
pred = branch and
first(this.getBranch(i + 1), succ) and
c.isValidFor(branch) and
c.(ConditionalCompletion).getValue() = false
)
or
@@ -1397,8 +1398,10 @@ module Trees {
final override ControlFlowTree getChildElement(int i) { result = this.getMethodName(i) }
}
private class WhenTree extends PreOrderTree, WhenClause {
final override predicate propagatesAbnormal(AstNode child) { child = this.getAPattern() }
private class WhenTree extends ControlFlowTree, WhenClause {
final override predicate propagatesAbnormal(AstNode child) {
child = [this.getAPattern(), this.getBody()]
}
final Expr getLastPattern() {
exists(int i |
@@ -1407,8 +1410,11 @@ module Trees {
)
}
final override predicate first(AstNode first) { first(this.getPattern(0), first) }
final override predicate last(AstNode last, Completion c) {
last(this.getLastPattern(), last, c) and
last = this and
c.isValidFor(this) and
c.(ConditionalCompletion).getValue() = false
or
last(this.getBody(), last, c)
@@ -1416,8 +1422,9 @@ module Trees {
final override predicate succ(AstNode pred, AstNode succ, Completion c) {
pred = this and
first(this.getPattern(0), succ) and
c instanceof SimpleCompletion
c.isValidFor(this) and
c.(ConditionalCompletion).getValue() = true and
first(this.getBody(), succ)
or
exists(int i, Expr p, boolean b |
p = this.getPattern(i) and
@@ -1425,10 +1432,13 @@ module Trees {
b = c.(ConditionalCompletion).getValue()
|
b = true and
first(this.getBody(), succ)
succ = this
or
b = false and
first(this.getPattern(i + 1), succ)
or
not exists(this.getPattern(i + 1)) and
succ = this
)
}
}

View File

@@ -907,9 +907,13 @@ module TestOutput {
query predicate edges(RelevantNode pred, RelevantNode succ, string attr, string val) {
attr = "semmle.label" and
exists(SuccessorType t | succ = getASuccessor(pred, t) |
if successorTypeIsSimple(t) then val = "" else val = t.toString()
)
val =
strictconcat(SuccessorType t, string s |
succ = getASuccessor(pred, t) and
if successorTypeIsSimple(t) then s = "" else s = t.toString()
|
s, ", " order by s
)
or
attr = "semmle.order" and
val =

View File

@@ -86,6 +86,10 @@ private module ConditionalCompletionSplitting {
last(succ.(ConditionalExpr).getBranch(_), pred, c) and
completion = c
)
or
succ(pred, succ, c) and
succ instanceof WhenClause and
completion = c
}
override predicate hasEntryScope(CfgScope scope, AstNode succ) { none() }

View File

@@ -7,22 +7,53 @@ private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.dataflow.SSA
private import codeql.ruby.ast.internal.Constant
private import codeql.ruby.InclusionTests
private import codeql.ruby.ast.internal.Literal
private predicate stringConstCompare(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
cached
private predicate stringConstCompare(CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch) {
exists(CfgNodes::ExprNodes::ComparisonOperationCfgNode c |
c = g and
c = guard and
exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode |
c.getExpr() instanceof EqExpr and branch = true
// Only consider strings without any interpolations
not strLitNode.getExpr().getComponent(_) instanceof StringInterpolationComponent and
c.getExpr() instanceof EqExpr and
branch = true
or
c.getExpr() instanceof CaseEqExpr and branch = true
or
c.getExpr() instanceof NEExpr and branch = false
|
c.getLeftOperand() = strLitNode and c.getRightOperand() = e
c.getLeftOperand() = strLitNode and c.getRightOperand() = testedNode
or
c.getLeftOperand() = e and c.getRightOperand() = strLitNode
c.getLeftOperand() = testedNode and c.getRightOperand() = strLitNode
)
)
or
stringConstCaseCompare(guard, testedNode, branch)
or
exists(CfgNodes::ExprNodes::BinaryOperationCfgNode g |
g = guard and
stringConstCompareOr(guard, branch) and
stringConstCompare(g.getLeftOperand(), testedNode, _)
)
}
/**
* Holds if `guard` is an `or` expression whose operands are string comparison guards.
* For example:
*
* ```rb
* x == "foo" or x == "bar"
* ```
*/
private predicate stringConstCompareOr(
CfgNodes::ExprNodes::BinaryOperationCfgNode guard, boolean branch
) {
guard.getExpr() instanceof LogicalOrExpr and
branch = true and
forall(CfgNode innerGuard | innerGuard = guard.getAnOperand() |
stringConstCompare(innerGuard, any(Ssa::Definition def).getARead(), branch)
)
}
/**
@@ -72,10 +103,13 @@ deprecated class StringConstCompare extends DataFlow::BarrierGuard,
}
}
private predicate stringConstArrayInclusionCall(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
cached
private predicate stringConstArrayInclusionCall(
CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch
) {
exists(InclusionTest t |
t.asExpr() = g and
e = t.getContainedNode().asExpr() and
t.asExpr() = guard and
testedNode = t.getContainedNode().asExpr() and
branch = t.getPolarity()
|
exists(ExprNodes::ArrayLiteralCfgNode arr |
@@ -132,3 +166,68 @@ deprecated class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
override predicate checks(CfgNode expr, boolean branch) { expr = checkedNode and branch = true }
}
/**
* A validation of a value by comparing with a constant string via a `case`
* expression. For example:
*
* ```rb
* name = params[:user_name]
* case name
* when "alice"
* User.find_by("username = #{name}")
* when *["bob", "charlie"]
* User.find_by("username = #{name}")
* when "dave", "eve" # this is not yet recognised as a barrier guard
* User.find_by("username = #{name}")
* end
* ```
*/
private predicate stringConstCaseCompare(
CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch
) {
branch = true and
exists(CfgNodes::ExprNodes::CaseExprCfgNode case |
case.getValue() = testedNode and
(
guard =
any(CfgNodes::ExprNodes::WhenClauseCfgNode branchNode |
branchNode = case.getBranch(_) and
// For simplicity, consider patterns that contain only string literals or arrays of string literals
forall(ExprCfgNode pattern | pattern = branchNode.getPattern(_) |
// when "foo"
// when "foo", "bar"
pattern instanceof ExprNodes::StringLiteralCfgNode
or
pattern =
any(CfgNodes::ExprNodes::SplatExprCfgNode splat |
// when *["foo", "bar"]
forex(ExprCfgNode elem |
elem = splat.getOperand().(ExprNodes::ArrayLiteralCfgNode).getAnArgument()
|
elem instanceof ExprNodes::StringLiteralCfgNode
)
or
// when *some_var
// when *SOME_CONST
exists(ExprNodes::ArrayLiteralCfgNode arr |
isArrayConstant(splat.getOperand(), arr) and
forall(ExprCfgNode elem | elem = arr.getAnArgument() |
elem instanceof ExprNodes::StringLiteralCfgNode
)
)
)
)
)
or
// in "foo"
exists(
CfgNodes::ExprNodes::InClauseCfgNode branchNode, ExprNodes::StringLiteralCfgNode pattern
|
branchNode = case.getBranch(_) and
pattern = branchNode.getPattern() and
guard = pattern
)
)
)
}

View File

@@ -0,0 +1,30 @@
/**
* Provides classes representing various flow steps for taint tracking.
*/
private import codeql.ruby.DataFlow
private import internal.DataFlowPrivate as DFPrivate
private class Unit = DFPrivate::Unit;
/**
* A module importing the frameworks that implement additional flow steps,
* ensuring that they are visible to the taint tracking library.
*/
private module Frameworks {
import codeql.ruby.frameworks.StringFormatters
}
/**
* A unit class for adding additional taint steps.
*
* Extend this class to add additional taint steps that should apply to all
* taint configurations.
*/
class AdditionalTaintStep extends Unit {
/**
* Holds if the step from `node1` to `node2` should be considered a taint
* step for all configurations.
*/
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
}

View File

@@ -494,6 +494,7 @@ private module Cached {
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name)
} or
THashSplatArgumentPosition() or
TSplatAllArgumentPosition() or
TAnyArgumentPosition() or
TAnyKeywordArgumentPosition()
@@ -515,6 +516,7 @@ private module Cached {
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordArgumentPosition(_, name)
} or
THashSplatParameterPosition() or
TSplatAllParameterPosition() or
TAnyParameterPosition() or
TAnyKeywordParameterPosition()
}
@@ -1146,6 +1148,8 @@ class ParameterPosition extends TParameterPosition {
/** Holds if this position represents a hash-splat parameter. */
predicate isHashSplat() { this = THashSplatParameterPosition() }
predicate isSplatAll() { this = TSplatAllParameterPosition() }
/**
* Holds if this position represents any parameter, except `self` parameters. This
* includes both positional, named, and block parameters.
@@ -1169,6 +1173,8 @@ class ParameterPosition extends TParameterPosition {
or
this.isHashSplat() and result = "**"
or
this.isSplatAll() and result = "*"
or
this.isAny() and result = "any"
or
this.isAnyNamed() and result = "any-named"
@@ -1204,6 +1210,8 @@ class ArgumentPosition extends TArgumentPosition {
*/
predicate isHashSplat() { this = THashSplatArgumentPosition() }
predicate isSplatAll() { this = TSplatAllArgumentPosition() }
/** Gets a textual representation of this position. */
string toString() {
this.isSelf() and result = "self"
@@ -1219,6 +1227,8 @@ class ArgumentPosition extends TArgumentPosition {
this.isAnyNamed() and result = "any-named"
or
this.isHashSplat() and result = "**"
or
this.isSplatAll() and result = "*"
}
}
@@ -1245,6 +1255,8 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
or
ppos.isHashSplat() and apos.isHashSplat()
or
ppos.isSplatAll() and apos.isSplatAll()
or
ppos.isAny() and argumentPositionIsNotSelf(apos)
or
apos.isAny() and parameterPositionIsNotSelf(ppos)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -915,6 +915,17 @@ private module Cached {
TDataFlowCallNone() or
TDataFlowCallSome(DataFlowCall call)
cached
newtype TParameterPositionOption =
TParameterPositionNone() or
TParameterPositionSome(ParameterPosition pos)
cached
newtype TReturnCtx =
TReturnCtxNone() or
TReturnCtxNoFlowThrough() or
TReturnCtxMaybeFlowThrough(ReturnKindExt kind)
cached
newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) }
@@ -1304,6 +1315,44 @@ class DataFlowCallOption extends TDataFlowCallOption {
}
}
/** An optional `ParameterPosition`. */
class ParameterPositionOption extends TParameterPositionOption {
string toString() {
this = TParameterPositionNone() and
result = "(none)"
or
exists(ParameterPosition pos |
this = TParameterPositionSome(pos) and
result = pos.toString()
)
}
}
/**
* A return context used to calculate flow summaries in reverse flow.
*
* The possible values are:
*
* - `TReturnCtxNone()`: no return flow.
* - `TReturnCtxNoFlowThrough()`: return flow, but flow through is not possible.
* - `TReturnCtxMaybeFlowThrough(ReturnKindExt kind)`: return flow, of kind `kind`, and
* flow through may be possible.
*/
class ReturnCtx extends TReturnCtx {
string toString() {
this = TReturnCtxNone() and
result = "(none)"
or
this = TReturnCtxNoFlowThrough() and
result = "(no flow through)"
or
exists(ReturnKindExt kind |
this = TReturnCtxMaybeFlowThrough(kind) and
result = kind.toString()
)
}
}
/** A `Content` tagged with the type of a containing object. */
class TypedContent extends MkTypedContent {
private Content c;

View File

@@ -241,6 +241,10 @@ private class Argument extends CfgNodes::ExprCfgNode {
this = call.getAnArgument() and
this.getExpr() instanceof HashSplatExpr and
arg.isHashSplat()
or
this = call.getArgument(0) and
this.getExpr() instanceof SplatExpr and
arg.isSplatAll()
}
/** Holds if this expression is the `i`th argument of `c`. */
@@ -276,7 +280,8 @@ private module Cached {
p instanceof SimpleParameter or
p instanceof OptionalParameter or
p instanceof KeywordParameter or
p instanceof HashSplatParameter
p instanceof HashSplatParameter or
p instanceof SplatParameter
} or
TSelfParameterNode(MethodBase m) or
TBlockParameterNode(MethodBase m) or
@@ -474,7 +479,7 @@ private module Cached {
// external model data. This, unfortunately, does not included any field names used
// in models defined in QL code.
exists(string input, string output |
ModelOutput::relevantSummaryModel(_, _, _, input, output, _)
ModelOutput::relevantSummaryModel(_, _, input, output, _)
|
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
)
@@ -616,6 +621,9 @@ private module ParameterNodes {
or
parameter = callable.getAParameter().(HashSplatParameter) and
pos.isHashSplat()
or
parameter = callable.getParameter(0).(SplatParameter) and
pos.isSplatAll()
)
}

View File

@@ -615,7 +615,7 @@ class ContentSet extends TContentSet {
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
* the argument `x`.
*/
signature predicate guardChecksSig(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch);
signature predicate guardChecksSig(CfgNodes::AstCfgNode g, CfgNode e, boolean branch);
/**
* Provides a set of barrier nodes for a guard that validates an expression.
@@ -625,13 +625,13 @@ signature predicate guardChecksSig(CfgNodes::ExprCfgNode g, CfgNode e, boolean b
*/
module BarrierGuard<guardChecksSig/3 guardChecks> {
pragma[nomagic]
private predicate guardChecksSsaDef(CfgNodes::ExprCfgNode g, boolean branch, Ssa::Definition def) {
private predicate guardChecksSsaDef(CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def) {
guardChecks(g, def.getARead(), branch)
}
pragma[nomagic]
private predicate guardControlsSsaDef(
CfgNodes::ExprCfgNode g, boolean branch, Ssa::Definition def, Node n
CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def, Node n
) {
def.getARead() = n.asExpr() and
guardControlsBlock(g, n.asExpr().getBasicBlock(), branch)
@@ -639,7 +639,7 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
/** Gets a node that is safely guarded by the given guard check. */
Node getABarrierNode() {
exists(CfgNodes::ExprCfgNode g, boolean branch, Ssa::Definition def |
exists(CfgNodes::AstCfgNode g, boolean branch, Ssa::Definition def |
guardChecksSsaDef(g, branch, def) and
guardControlsSsaDef(g, branch, def, result)
)
@@ -669,8 +669,8 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
}
/** Holds if the guard `guard` controls block `bb` upon evaluating to `branch`. */
private predicate guardControlsBlock(CfgNodes::ExprCfgNode guard, BasicBlock bb, boolean branch) {
exists(ConditionBlock conditionBlock, SuccessorTypes::BooleanSuccessor s |
private predicate guardControlsBlock(CfgNodes::AstCfgNode guard, BasicBlock bb, boolean branch) {
exists(ConditionBlock conditionBlock, SuccessorTypes::ConditionalSuccessor s |
guard = conditionBlock.getLastNode() and
s.getValue() = branch and
conditionBlock.controls(bb, s)

View File

@@ -2,6 +2,7 @@ private import codeql.ssa.Ssa as SsaImplCommon
private import codeql.ruby.AST
private import codeql.ruby.CFG as Cfg
private import codeql.ruby.controlflow.internal.ControlFlowGraphImplShared as ControlFlowGraphImplShared
private import codeql.ruby.dataflow.SSA
private import codeql.ruby.ast.Variable
private import Cfg::CfgNodes::ExprNodes
@@ -61,7 +62,23 @@ private module SsaInput implements SsaImplCommon::InputSig {
}
}
import SsaImplCommon::Make<SsaInput>
private import SsaImplCommon::Make<SsaInput> as Impl
class Definition = Impl::Definition;
class WriteDefinition = Impl::WriteDefinition;
class UncertainWriteDefinition = Impl::UncertainWriteDefinition;
class PhiNode = Impl::PhiNode;
module Consistency = Impl::Consistency;
module ExposedForTestingOnly {
predicate ssaDefReachesReadExt = Impl::ssaDefReachesReadExt/4;
predicate phiHasInputFromBlockExt = Impl::phiHasInputFromBlockExt/3;
}
/** Holds if `v` is uninitialized at index `i` in entry block `bb`. */
predicate uninitializedWrite(Cfg::EntryBasicBlock bb, int i, LocalVariable v) {
@@ -204,7 +221,7 @@ private predicate adjacentDefRead(
Definition def, SsaInput::BasicBlock bb1, int i1, SsaInput::BasicBlock bb2, int i2,
SsaInput::SourceVariable v
) {
adjacentDefRead(def, bb1, i1, bb2, i2) and
Impl::adjacentDefRead(def, bb1, i1, bb2, i2) and
v = def.getSourceVariable()
}
@@ -220,7 +237,7 @@ private predicate adjacentDefReachesRead(
exists(SsaInput::BasicBlock bb3, int i3 |
adjacentDefReachesRead(def, bb1, i1, bb3, i3) and
SsaInput::variableRead(bb3, i3, _, false) and
adjacentDefRead(def, bb3, i3, bb2, i2)
Impl::adjacentDefRead(def, bb3, i3, bb2, i2)
)
}
@@ -243,11 +260,11 @@ private predicate adjacentDefReachesUncertainRead(
/** Same as `lastRefRedef`, but skips uncertain reads. */
pragma[nomagic]
private predicate lastRefSkipUncertainReads(Definition def, SsaInput::BasicBlock bb, int i) {
lastRef(def, bb, i) and
Impl::lastRef(def, bb, i) and
not SsaInput::variableRead(bb, i, def.getSourceVariable(), false)
or
exists(SsaInput::BasicBlock bb0, int i0 |
lastRef(def, bb0, i0) and
Impl::lastRef(def, bb0, i0) and
adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
)
}
@@ -304,7 +321,7 @@ private module Cached {
cached
VariableReadAccessCfgNode getARead(Definition def) {
exists(LocalVariable v, Cfg::BasicBlock bb, int i |
ssaDefReachesRead(v, def, bb, i) and
Impl::ssaDefReachesRead(v, def, bb, i) and
variableReadActual(bb, i, v) and
result = bb.getNode(i)
)
@@ -315,7 +332,7 @@ private module Cached {
Definition def, CallCfgNode call, LocalVariable v, Cfg::CfgScope scope
) {
exists(Cfg::BasicBlock bb, int i |
ssaDefReachesRead(v, def, bb, i) and
Impl::ssaDefReachesRead(v, def, bb, i) and
capturedCallRead(call, bb, i, v) and
scope.getOuterCfgScope() = bb.getScope()
)
@@ -360,7 +377,7 @@ private module Cached {
Definition def, LocalVariable v, Cfg::CfgScope scope
) {
exists(Cfg::BasicBlock bb, int i |
ssaDefReachesRead(v, def, bb, i) and
Impl::ssaDefReachesRead(v, def, bb, i) and
capturedExitRead(bb, i, v) and
scope = bb.getScope().getOuterCfgScope*()
)
@@ -403,7 +420,7 @@ private module Cached {
cached
Definition phiHasInputFromBlock(PhiNode phi, Cfg::BasicBlock bb) {
phiHasInputFromBlock(phi, result, bb)
Impl::phiHasInputFromBlock(phi, result, bb)
}
/**
@@ -459,19 +476,45 @@ private module Cached {
*/
cached
predicate lastRefBeforeRedef(Definition def, Cfg::BasicBlock bb, int i, Definition next) {
lastRefRedef(def, bb, i, next) and
Impl::lastRefRedef(def, bb, i, next) and
not SsaInput::variableRead(bb, i, def.getSourceVariable(), false)
or
exists(SsaInput::BasicBlock bb0, int i0 |
lastRefRedef(def, bb0, i0, next) and
Impl::lastRefRedef(def, bb0, i0, next) and
adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
)
}
cached
Definition uncertainWriteDefinitionInput(UncertainWriteDefinition def) {
uncertainWriteDefinitionInput(def, result)
Impl::uncertainWriteDefinitionInput(def, result)
}
}
import Cached
/**
* An extended static single assignment (SSA) definition.
*
* This is either a normal SSA definition (`Definition`) or a
* phi-read node (`PhiReadNode`).
*
* Only intended for internal use.
*/
class DefinitionExt extends Impl::DefinitionExt {
override string toString() { result = this.(Ssa::Definition).toString() }
/** Gets the location of this definition. */
Location getLocation() { result = this.(Ssa::Definition).getLocation() }
}
/**
* A phi-read node.
*
* Only intended for internal use.
*/
class PhiReadNode extends DefinitionExt, Impl::PhiReadNode {
override string toString() { result = "SSA phi read(" + this.getSourceVariable() + ")" }
override Location getLocation() { result = this.getBasicBlock().getLocation() }
}

View File

@@ -62,6 +62,8 @@ private CfgNodes::ExprNodes::VariableWriteAccessCfgNode variablesInPattern(
cached
private module Cached {
private import codeql.ruby.dataflow.FlowSteps as FlowSteps
cached
predicate forceCachingInSameStage() { any() }
@@ -93,12 +95,10 @@ private module Cached {
)
)
or
// string interpolation of `nodeFrom` into `nodeTo`
nodeFrom.asExpr() =
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false)
or
any(FlowSteps::AdditionalTaintStep s).step(nodeFrom, nodeTo)
or
// Although flow through collections is modeled precisely using stores/reads, we still
// allow flow out of a _tainted_ collection. This is needed in order to support taint-
// tracking configurations where the source is a collection.

View File

@@ -12,6 +12,7 @@ private import codeql.ruby.frameworks.ActionDispatch
private import codeql.ruby.frameworks.ActionView
private import codeql.ruby.frameworks.Rails
private import codeql.ruby.frameworks.internal.Rails
private import codeql.ruby.dataflow.internal.DataFlowDispatch
/**
* DEPRECATED: Import `codeql.ruby.frameworks.Rails` and use `Rails::ParamsCall` instead.
@@ -295,7 +296,7 @@ private module Request {
/** A method call on `request` which returns the request body. */
private class BodyCall extends RequestInputAccess {
BodyCall() { this.getMethodName() = ["body", "raw_post"] }
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
}
@@ -538,12 +539,34 @@ private class ActionControllerProtectFromForgeryCall extends CsrfProtectionSetti
/**
* A call to `send_file`, which sends the file at the given path to the client.
*/
private class SendFile extends FileSystemAccess::Range, DataFlow::CallNode {
private class SendFile extends FileSystemAccess::Range, Http::Server::HttpResponse::Range,
DataFlow::CallNode {
SendFile() {
this = [actionControllerInstance(), Response::response()].getAMethodCall("send_file")
}
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
override DataFlow::Node getBody() { result = this.getArgument(0) }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "application/octet-stream" }
}
/**
* A call to `send_data`, which sends the given data to the client.
*/
class SendDataCall extends DataFlow::CallNode, Http::Server::HttpResponse::Range {
SendDataCall() {
this = [actionControllerInstance(), Response::response()].getAMethodCall("send_data")
}
override DataFlow::Node getBody() { result = this.getArgument(0) }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
override string getMimetypeDefault() { result = "application/octet-stream" }
}
private module ParamsSummaries {
@@ -733,3 +756,28 @@ private module Response {
override DataFlow::Node getValue() { result = this.getArgument(0) }
}
}
private class ActionControllerLoggerInstance extends DataFlow::Node {
ActionControllerLoggerInstance() {
this = actionControllerInstance().getAMethodCall("logger")
or
any(ActionControllerLoggerInstance i).(DataFlow::LocalSourceNode).flowsTo(this)
}
}
private class ActionControllerLoggingCall extends DataFlow::CallNode, Logging::Range {
ActionControllerLoggingCall() {
this.getReceiver() instanceof ActionControllerLoggerInstance and
this.getMethodName() = ["debug", "error", "fatal", "info", "unknown", "warn"]
}
// Note: this is identical to the definition `stdlib.Logger.LoggerInfoStyleCall`.
override DataFlow::Node getAnInput() {
// `msg` from `Logger#info(msg)`,
// or `progname` from `Logger#info(progname) <block>`
result = this.getArgument(0)
or
// a return value from the block in `Logger#info(progname) <block>`
exprNodeReturnedFrom(result, this.getBlock().asExpr().getExpr())
}
}

View File

@@ -21,23 +21,21 @@ module ActionDispatch {
*/
private class MimeTypeTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// package1;type1;package2;type2;path
// type1;type2;path
row =
[
// Mime[type] : Mime::Type (omitted)
// Method names with brackets like [] cannot be represented in MaD.
// Mime.fetch(type) : Mime::Type
"actiondispatch;Mime::Type;;;Member[Mime].Method[fetch].ReturnValue",
// Mime::Type.new(str) : Mime::Type
"actiondispatch;Mime::Type;;;Member[Mime].Member[Type].Instance",
"Mime::Type;Mime!;Method[fetch].ReturnValue",
// Mime::Type.lookup(str) : Mime::Type
"actiondispatch;Mime::Type;;;Member[Mime].Member[Type].Method[lookup].ReturnValue",
"Mime::Type;Mime::Type!;Method[lookup].ReturnValue",
// Mime::Type.lookup_by_extension(str) : Mime::Type
"actiondispatch;Mime::Type;;;Member[Mime].Member[Type].Method[lookup_by_extension].ReturnValue",
"Mime::Type;Mime::Type!;Method[lookup_by_extension].ReturnValue",
// Mime::Type.register(str) : Mime::Type
"actiondispatch;Mime::Type;;;Member[Mime].Member[Type].Method[register].ReturnValue",
"Mime::Type;Mime::Type!;Method[register].ReturnValue",
// Mime::Type.register_alias(str) : Mime::Type
"actiondispatch;Mime::Type;;;Member[Mime].Member[Type].Method[register_alias].ReturnValue",
"Mime::Type;Mime::Type!;Method[register_alias].ReturnValue",
]
}
}
@@ -48,10 +46,7 @@ module ActionDispatch {
*/
class MimeTypeMatchRegExpInterpretation extends RE::RegExpInterpretation::Range {
MimeTypeMatchRegExpInterpretation() {
this =
ModelOutput::getATypeNode("actiondispatch", "Mime::Type")
.getAMethodCall(["match?", "=~"])
.getArgument(0)
this = ModelOutput::getATypeNode("Mime::Type").getAMethodCall(["match?", "=~"]).getArgument(0)
}
}

View File

@@ -31,8 +31,8 @@ module ActiveStorage {
override predicate row(string row) {
row =
[
"activestorage;;Member[ActiveStorage].Member[Filename].Method[new];Argument[0];ReturnValue;taint",
"activestorage;;Member[ActiveStorage].Member[Filename].Instance.Method[sanitized];Argument[self];ReturnValue;taint",
"ActiveStorage::Filename!;Method[new];Argument[0];ReturnValue;taint",
"ActiveStorage::Filename;Method[sanitized];Argument[self];ReturnValue;taint",
]
}
}
@@ -45,25 +45,23 @@ module ActiveStorage {
// package1;type1;package2;type2;path
row =
[
// ActiveStorage::Blob.new : Blob
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Instance",
// ActiveStorage::Blob.create_and_upload! : Blob
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[create_and_upload!].ReturnValue",
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[create_and_upload!].ReturnValue",
// ActiveStorage::Blob.create_before_direct_upload! : Blob
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[create_before_direct_upload!].ReturnValue",
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[create_before_direct_upload!].ReturnValue",
// ActiveStorage::Blob.compose(blobs : [Blob]) : Blob
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[compose].ReturnValue",
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[compose].ReturnValue",
// gives error: Invalid name 'Element' in access path
// "activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[compose].Argument[0].Element[any]",
// "ActiveStorage::Blob;ActiveStorage::Blob!;Method[compose].Argument[0].Element[any]",
// ActiveStorage::Blob.find_signed(!) : Blob
"activestorage;Blob;activestorage;;Member[ActiveStorage].Member[Blob].Method[find_signed,find_signed!].ReturnValue",
"ActiveStorage::Blob;ActiveStorage::Blob!;Method[find_signed,find_signed!].ReturnValue",
]
}
}
private class BlobInstance extends DataFlow::Node {
BlobInstance() {
this = ModelOutput::getATypeNode("activestorage", "Blob").getAValueReachableFromSource()
this = ModelOutput::getATypeNode("ActiveStorage::Blob").getAValueReachableFromSource()
or
// ActiveStorage::Attachment#blob : Blob
exists(DataFlow::CallNode call |
@@ -168,7 +166,7 @@ module ActiveStorage {
* A call on an ActiveStorage object that results in an image transformation.
* Arguments to these calls may be executed as system commands.
*/
private class ImageProcessingCall extends DataFlow::CallNode, SystemCommandExecution::Range {
private class ImageProcessingCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
ImageProcessingCall() {
this.getReceiver() instanceof BlobInstance and
this.getMethodName() = ["variant", "preview", "representation"]
@@ -209,7 +207,7 @@ module ActiveStorage {
this = API::getTopLevelMember("ActiveStorage").getAMethodCall("video_preview_arguments=")
}
override DataFlow::Node getAnArgument() { result = this.getArgument(0) }
override DataFlow::Node getAnArgument() { result = super.getArgument(0) }
}
/**

View File

@@ -104,6 +104,17 @@ module ActiveSupport {
override predicate runsArbitraryCode() { none() }
}
/** Flow summary for `Object#to_json`, which serializes the receiver as a JSON string. */
private class ToJsonSummary extends SimpleSummarizedCallable {
ToJsonSummary() { this = "to_json" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = ["Argument[self]", "Argument[self].Element[any]"] and
output = "ReturnValue" and
preservesValue = false
}
}
}
/**
@@ -284,7 +295,153 @@ module ActiveSupport {
preservesValue = true
}
}
// TODO: index_by, index_with, pick, pluck (they require Hash dataflow)
private class IndexBySummary extends SimpleSummarizedCallable {
IndexBySummary() { this = "index_by" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and
preservesValue = true
}
}
private class IndexWithSummary extends SimpleSummarizedCallable {
IndexWithSummary() { this = "index_with" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[0]" and
preservesValue = true
or
input = ["Argument[0]", "Argument[block].ReturnValue"] and
output = "ReturnValue.Element[?]" and
preservesValue = true
}
}
private string getKeyArgument(MethodCall mc, int i) {
mc.getMethodName() = ["pick", "pluck"] and
result = DataFlow::Content::getKnownElementIndex(mc.getArgument(i)).serialize()
}
private class PickSingleSummary extends SummarizedCallable {
private MethodCall mc;
private string key;
PickSingleSummary() {
key = getKeyArgument(mc, 0) and
this = "Enumerable.pick(" + key + ")" and
mc.getMethodName() = "pick" and
mc.getNumberOfArguments() = 1
}
override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[0].Element[" + key + "]" and
output = "ReturnValue" and
preservesValue = true
}
}
private class PickMultipleSummary extends SummarizedCallable {
private MethodCall mc;
PickMultipleSummary() {
mc.getMethodName() = "pick" and
mc.getNumberOfArguments() > 1 and
exists(int maxKey |
maxKey = max(int j | exists(getKeyArgument(mc, j))) and
this =
"Enumerable.pick(" +
concat(int i, string key |
key = getKeyArgument(mc, i)
or
key = "_" and
not exists(getKeyArgument(mc, i)) and
i in [0 .. maxKey]
|
key, "," order by i
) + ")"
)
}
override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
exists(string s, int i |
s = getKeyArgument(mc, i) and
input = "Argument[self].Element[0].Element[" + s + "]" and
output = "ReturnValue.Element[" + i + "]"
) and
preservesValue = true
}
}
private class PluckSingleSummary extends SummarizedCallable {
private MethodCall mc;
private string key;
PluckSingleSummary() {
key = getKeyArgument(mc, 0) and
this = "Enumerable.pluck(" + key + ")" and
mc.getMethodName() = "pluck" and
mc.getNumberOfArguments() = 1
}
override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any].Element[" + key + "]" and
output = "ReturnValue.Element[any]" and
preservesValue = true
}
}
private class PluckMultipleSummary extends SummarizedCallable {
private MethodCall mc;
PluckMultipleSummary() {
mc.getMethodName() = "pluck" and
mc.getNumberOfArguments() > 1 and
exists(int maxKey |
maxKey = max(int j | exists(getKeyArgument(mc, j))) and
this =
"Enumerable.pluck(" +
concat(int i, string key |
key = getKeyArgument(mc, i)
or
key = "_" and
not exists(getKeyArgument(mc, i)) and
i in [0 .. maxKey]
|
key, "," order by i
) + ")"
)
}
override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
exists(string s, int i |
s = getKeyArgument(mc, i) and
input = "Argument[self].Element[any].Element[" + s + "]" and
output = "ReturnValue.Element[?].Element[" + i + "]"
) and
preservesValue = true
}
}
private class SoleSummary extends SimpleSummarizedCallable {
SoleSummary() { this = "sole" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[0]" and
output = "ReturnValue" and
preservesValue = true
}
}
}
}
@@ -302,14 +459,34 @@ module ActiveSupport {
}
}
/**
* `ActiveSupport::ERB`
*/
module Erb {
/**
* `ActiveSupport::ERB::Util`
*/
module Util {
private class JsonEscapeSummary extends SimpleSummarizedCallable {
JsonEscapeSummary() { this = "json_escape" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0]" and
output = "ReturnValue" and
preservesValue = false
}
}
}
}
/**
* Type summaries for extensions to the `Pathname` module.
*/
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// package1;type1;package2;type2;path
// type1;type2;path
// Pathname#existence : Pathname
row = ";Pathname;;Pathname;Method[existence].ReturnValue"
row = "Pathname;Pathname;Method[existence].ReturnValue"
}
}
@@ -317,7 +494,7 @@ module ActiveSupport {
private class PathnameTaintSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
// Pathname#existence
row = ";Pathname;Method[existence];Argument[self];ReturnValue;taint"
row = "Pathname;Method[existence];Argument[self];ReturnValue;taint"
}
}
@@ -335,13 +512,26 @@ module ActiveSupport {
row =
[
// SafeBuffer.new(x) does not sanitize x
"activesupport;;Member[ActionView].Member[SafeBuffer].Method[new];Argument[0];ReturnValue;taint",
"ActionView::SafeBuffer!;Method[new];Argument[0];ReturnValue;taint",
// SafeBuffer#safe_concat(x) does not sanitize x
"activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat];Argument[0];ReturnValue;taint",
"activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat];Argument[0];Argument[self];taint",
"ActionView::SafeBuffer;Method[safe_concat];Argument[0];ReturnValue;taint",
"ActionView::SafeBuffer;Method[safe_concat];Argument[0];Argument[self];taint",
// These methods preserve taint in self
"activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[concat,insert,prepend,to_s,to_param];Argument[self];ReturnValue;taint",
"ActionView::SafeBuffer;Method[concat,insert,prepend,to_s,to_param];Argument[self];ReturnValue;taint",
]
}
}
/** `ActiveSupport::JSON` */
module Json {
private class JsonSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
"ActiveSupport::JSON!;Method[encode,dump];Argument[0];ReturnValue;taint",
"ActiveSupport::JSON!;Method[decode,load];Argument[0];ReturnValue;taint",
]
}
}
}
}

View File

@@ -6,6 +6,7 @@
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.Concepts
/**
* Provides modeling for Arel, a low level SQL library that powers ActiveRecord.
@@ -28,4 +29,14 @@ module Arel {
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
}
}
/** A call to `Arel.sql`, considered as a SQL construction. */
private class ArelSqlConstruction extends SqlConstruction::Range, DataFlow::CallNode {
ArelSqlConstruction() {
this = DataFlow::getConstant("Arel").getAMethodCall() and
this.getMethodName() = "sql"
}
override DataFlow::Node getSql() { result = this.getArgument(0) }
}
}

View File

@@ -0,0 +1,22 @@
/** Provides modeling for the `json` gem. */
private import codeql.ruby.frameworks.data.ModelsAsData
/** Provides modeling for the `json` gem. */
module Json {
/**
* Flow summaries for common `JSON` methods.
* Not all of these methods are strictly defined in the `json` gem.
* The `JSON` namespace is heavily overloaded by other JSON parsing gems such as `oj`, `json_pure`, `multi_json` etc.
* This summary covers common methods we've seen called on `JSON` in the wild.
*/
private class JsonSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
"JSON!;Method[parse,parse!,load,restore];Argument[0];ReturnValue;taint",
"JSON!;Method[generate,fast_generate,pretty_generate,dump,unparse,fast_unparse];Argument[0];ReturnValue;taint",
]
}
}
}

View File

@@ -20,7 +20,7 @@ module PosixSpawn {
/**
* A call to `POSIX::Spawn::Child.new` or `POSIX::Spawn::Child.build`.
*/
class ChildCall extends SystemCommandExecution::Range, DataFlow::CallNode {
class ChildCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
ChildCall() {
this =
[
@@ -30,7 +30,7 @@ module PosixSpawn {
}
override DataFlow::Node getAnArgument() {
result = this.getArgument(_) and not result.asExpr() instanceof ExprNodes::PairCfgNode
result = super.getArgument(_) and not result.asExpr() instanceof ExprNodes::PairCfgNode
}
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
@@ -39,7 +39,7 @@ module PosixSpawn {
/**
* A call to `POSIX::Spawn.spawn` or a related method.
*/
class SystemCall extends SystemCommandExecution::Range, DataFlow::CallNode {
class SystemCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
SystemCall() {
this =
posixSpawnModule()
@@ -71,7 +71,7 @@ module PosixSpawn {
}
private predicate argument(DataFlow::Node arg) {
arg = this.getArgument(_) and
arg = super.getArgument(_) and
not arg.asExpr() instanceof ExprNodes::HashLiteralCfgNode and
not arg.asExpr() instanceof ExprNodes::ArrayLiteralCfgNode and
not arg.asExpr() instanceof ExprNodes::PairCfgNode

View File

@@ -27,12 +27,12 @@ module Railties {
* A call to `Rails::Generators::Actions#execute_command`.
* This method concatenates its first and second arguments and executes the result as a shell command.
*/
private class ExecuteCommandCall extends SystemCommandExecution::Range, DataFlow::CallNode {
private class ExecuteCommandCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
ExecuteCommandCall() {
this = generatorsActionsClass().getAnInstanceSelf().getAMethodCall("execute_command")
}
override DataFlow::Node getAnArgument() { result = this.getArgument([0, 1]) }
override DataFlow::Node getAnArgument() { result = super.getArgument([0, 1]) }
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
}
@@ -40,7 +40,7 @@ module Railties {
/**
* A call to a method in `Rails::Generators::Actions` which delegates to `execute_command`.
*/
private class ExecuteCommandWrapperCall extends SystemCommandExecution::Range, DataFlow::CallNode {
private class ExecuteCommandWrapperCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
ExecuteCommandWrapperCall() {
this =
generatorsActionsClass()
@@ -48,7 +48,7 @@ module Railties {
.getAMethodCall(["rake", "rails_command", "git"])
}
override DataFlow::Node getAnArgument() { result = this.getArgument(0) }
override DataFlow::Node getAnArgument() { result = super.getArgument(0) }
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
}

View File

@@ -0,0 +1,135 @@
/**
* Provides classes for modeling string formatting libraries.
*/
private import codeql.ruby.AST as Ast
private import codeql.ruby.DataFlow
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.core.IO
/**
* A call to `printf` or `sprintf`.
*/
abstract class PrintfStyleCall extends DataFlow::CallNode {
// We assume that most printf-like calls have the signature f(format_string, args...)
/**
* Gets the format string of this call.
*/
DataFlow::Node getFormatString() { result = this.getArgument(0) }
/**
* Gets then `n`th formatted argument of this call.
*/
DataFlow::Node getFormatArgument(int n) { n >= 0 and result = this.getArgument(n + 1) }
/** Holds if this call returns the formatted string. */
predicate returnsFormatted() { any() }
}
/**
* A call to `Kernel.printf`.
*/
class KernelPrintfCall extends PrintfStyleCall {
KernelPrintfCall() {
this = API::getTopLevelMember("Kernel").getAMethodCall("printf")
or
this.asExpr().getExpr() instanceof Ast::UnknownMethodCall and
this.getMethodName() = "printf"
}
// Kernel#printf supports two signatures:
// printf(io, string, ...)
// printf(string, ...)
override DataFlow::Node getFormatString() {
// Because `printf` has two different signatures, we can't be sure which
// argument is the format string, so we use a heuristic:
// If the first argument has a string value, then we assume it is the format string.
// Otherwise we treat both the first and second args as the format string.
if this.getArgument(0).getExprNode().getConstantValue().isString(_)
then result = this.getArgument(0)
else result = this.getArgument([0, 1])
}
override predicate returnsFormatted() { none() }
}
/**
* A call to `Kernel.sprintf`.
*/
class KernelSprintfCall extends PrintfStyleCall {
KernelSprintfCall() {
this = API::getTopLevelMember("Kernel").getAMethodCall("sprintf")
or
this.asExpr().getExpr() instanceof Ast::UnknownMethodCall and
this.getMethodName() = "sprintf"
}
override predicate returnsFormatted() { any() }
}
/**
* A call to `IO#printf`.
*/
class IOPrintfCall extends PrintfStyleCall {
IOPrintfCall() {
this.getReceiver() instanceof IO::IOInstance and this.getMethodName() = "printf"
}
override predicate returnsFormatted() { none() }
}
/**
* A call to `String#%`.
*/
class StringPercentCall extends PrintfStyleCall {
StringPercentCall() { this.getMethodName() = "%" }
override DataFlow::Node getFormatString() { result = this.getReceiver() }
override DataFlow::Node getFormatArgument(int n) {
exists(CfgNodes::ExprNodes::ArrayLiteralCfgNode arrLit | arrLit = this.getArgument(0).asExpr() |
result.asExpr() = arrLit.getArgument(n)
)
or
exists(CfgNodes::ExprNodes::HashLiteralCfgNode hashLit |
hashLit = this.getArgument(0).asExpr()
|
n = -2 and // -2 is indicates that the index does not make sense in this context
result.asExpr() = hashLit.getAKeyValuePair().getValue()
)
}
override predicate returnsFormatted() { any() }
}
private import codeql.ruby.dataflow.FlowSteps
private import codeql.ruby.CFG
/**
* A step for string interpolation of `pred` into `succ`.
* E.g.
* ```rb
* succ = "foo #{pred} bar"
* ```
*/
private class StringLiteralFormatStep extends AdditionalTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred.asExpr() = succ.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
}
}
/**
* A taint propagating data flow edge arising from string formatting.
*/
private class StringFormattingTaintStep extends AdditionalTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(PrintfStyleCall call |
call.returnsFormatted() and
succ = call
|
pred = call.getFormatString()
or
pred = call.getFormatArgument(_)
)
}
}

View File

@@ -114,7 +114,7 @@ module IO {
* ```
* Ruby documentation: https://docs.ruby-lang.org/en/3.1/IO.html#method-c-popen
*/
class POpenCall extends SystemCommandExecution::Range, DataFlow::CallNode {
class POpenCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
POpenCall() { this = API::getTopLevelMember("IO").getAMethodCall("popen") }
override DataFlow::Node getAnArgument() { this.argument(result, _) }
@@ -131,7 +131,7 @@ module IO {
not n instanceof ExprNodes::ArrayLiteralCfgNode and
(
// IO.popen({var: "a"}, "cmd", {some: :opt})
arg = this.getArgument([0, 1]) and
arg = super.getArgument([0, 1]) and
// We over-approximate by assuming a subshell if the argument isn't an array or "-".
// This increases the sensitivity of the CommandInjection query at the risk of some FPs.
if n.getConstantValue().getString() = "-" then shell = false else shell = true
@@ -139,7 +139,7 @@ module IO {
// IO.popen([{var: "b"}, "cmd", "arg1", "arg2", {some: :opt}])
// IO.popen({var: "a"}, ["cmd", "arg1", "arg2", {some: :opt}])
shell = false and
exists(ExprNodes::ArrayLiteralCfgNode arr | this.getArgument([0, 1]).asExpr() = arr |
exists(ExprNodes::ArrayLiteralCfgNode arr | super.getArgument([0, 1]).asExpr() = arr |
n = arr.getAnArgument()
or
// IO.popen([{var: "b"}, ["cmd", "argv0"], "arg1", "arg2", {some: :opt}])

View File

@@ -13,7 +13,7 @@ module Regexp {
/** A flow summary for `Regexp.escape` and its alias, `Regexp.quote`. */
class RegexpEscapeSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row = ";;Member[Regexp].Method[escape,quote];Argument[0];ReturnValue;taint"
row = "Regexp!;Method[escape,quote];Argument[0];ReturnValue;taint"
}
}
}

View File

@@ -33,26 +33,23 @@ private class RemoteFlowSourceFromCsv extends RemoteFlowSource::Range {
}
private class SummarizedCallableFromModel extends SummarizedCallable {
string package;
string type;
string path;
SummarizedCallableFromModel() {
ModelOutput::relevantSummaryModel(package, type, path, _, _, _) and
this = package + ";" + type + ";" + path
ModelOutput::relevantSummaryModel(type, path, _, _, _) and
this = type + ";" + path
}
override Call getACall() {
exists(API::MethodAccessNode base |
ModelOutput::resolvedSummaryBase(package, type, path, base) and
ModelOutput::resolvedSummaryBase(type, path, base) and
result = base.getCallNode().asExpr().getExpr()
)
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
exists(string kind |
ModelOutput::relevantSummaryModel(package, type, path, input, output, kind)
|
exists(string kind | ModelOutput::relevantSummaryModel(type, path, input, output, kind) |
kind = "value" and
preservesValue = true
or

View File

@@ -5,23 +5,20 @@
*
* The CSV specification has the following columns:
* - Sources:
* `package; type; path; kind`
* `type; path; kind`
* - Sinks:
* `package; type; path; kind`
* `type; path; kind`
* - Summaries:
* `package; type; path; input; output; kind`
* `type; path; input; output; kind`
* - Types:
* `package1; type1; package2; type2; path`
* `type1; type2; path`
*
* The interpretation of a row is similar to API-graphs with a left-to-right
* reading.
* 1. The `package` column selects a package name, as it would be referenced in the source code,
* such as an NPM package, PIP package, or Ruby gem. (See `ModelsAsData.qll` for language-specific details).
* It may also be a synthetic package used for a type definition (see type definitions below).
* 2. The `type` column selects all instances of a named type originating from that package,
* or the empty string if referring to the package itself.
* 1. The `type` column selects all instances of a named type. The syntax of this column is language-specific.
* The language defines some type names that the analysis knows how to identify without models.
* It can also be a synthetic type name defined by a type definition (see type definitions below).
* 3. The `path` column is a `.`-separated list of "access path tokens" to resolve, starting at the node selected by `package` and `type`.
* 2. The `path` column is a `.`-separated list of "access path tokens" to resolve, starting at the node selected by `type`.
*
* Every language supports the following tokens:
* - Argument[n]: the n-th argument to a call. May be a range of form `x..y` (inclusive) and/or a comma-separated list.
@@ -42,10 +39,10 @@
*
* For the time being, please consult `ApiGraphModelsSpecific.qll` to see which language-specific tokens are currently supported.
*
* 4. The `input` and `output` columns specify how data enters and leaves the element selected by the
* first `(package, type, path)` tuple. Both strings are `.`-separated access paths
* 3. The `input` and `output` columns specify how data enters and leaves the element selected by the
* first `(type, path)` tuple. Both strings are `.`-separated access paths
* of the same syntax as the `path` column.
* 5. The `kind` column is a tag that can be referenced from QL to determine to
* 4. The `kind` column is a tag that can be referenced from QL to determine to
* which classes the interpreted elements should be added. For example, for
* sources `"remote"` indicates a default remote flow source, and for summaries
* `"taint"` indicates a default additional taint step and `"value"` indicates a
@@ -53,17 +50,17 @@
*
* ### Types
*
* A type row of form `package1; type1; package2; type2; path` indicates that `package2; type2; path`
* should be seen as an instance of the type `package1; type1`.
* A type row of form `type1; type2; path` indicates that `type2; path`
* should be seen as an instance of the type `type1`.
*
* A `(package,type)` pair may refer to a static type or a synthetic type name used internally in the model.
* A type may refer to a static type or a synthetic type name used internally in the model.
* Synthetic type names can be used to reuse intermediate sub-paths, when there are multiple ways to access the same
* element.
* See `ModelsAsData.qll` for the language-specific interpretation of packages and static type names.
* See `ModelsAsData.qll` for the language-specific interpretation of type names.
*
* By convention, if one wants to avoid clashes with static types from the package, the type name
* should be prefixed with a tilde character (`~`). For example, `(foo, ~Bar)` can be used to indicate that
* the type is related to the `foo` package but is not intended to match a static type.
* By convention, if one wants to avoid clashes with static types, the type name
* should be prefixed with a tilde character (`~`). For example, `~Bar` can be used to indicate that
* the type is not intended to match a static type.
*/
private import ApiGraphModelsSpecific as Specific
@@ -89,9 +86,9 @@ module ModelInput {
*
* A row of form
* ```
* package;type;path;kind
* type;path;kind
* ```
* indicates that the value at `(package, type, path)` should be seen as a flow
* indicates that the value at `(type, path)` should be seen as a flow
* source of the given `kind`.
*
* The kind `remote` represents a general remote flow source.
@@ -110,9 +107,9 @@ module ModelInput {
*
* A row of form
* ```
* package;type;path;kind
* type;path;kind
* ```
* indicates that the value at `(package, type, path)` should be seen as a sink
* indicates that the value at `(type, path)` should be seen as a sink
* of the given `kind`.
*/
abstract predicate row(string row);
@@ -129,9 +126,9 @@ module ModelInput {
*
* A row of form
* ```
* package;type;path;input;output;kind
* type;path;input;output;kind
* ```
* indicates that for each call to `(package, type, path)`, the value referred to by `input`
* indicates that for each call to `(type, path)`, the value referred to by `input`
* can flow to the value referred to by `output`.
*
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
@@ -151,9 +148,9 @@ module ModelInput {
*
* A row of form,
* ```
* package1;type1;package2;type2;path
* type1;type2;path
* ```
* indicates that `(package2, type2, path)` should be seen as an instance of `(package1, type1)`.
* indicates that `(type2, path)` should be seen as an instance of `type1`.
*/
abstract predicate row(string row);
}
@@ -163,28 +160,28 @@ module ModelInput {
*/
class TypeModel extends Unit {
/**
* Gets a data-flow node that is a source of the type `package;type`.
* Gets a data-flow node that is a source of the given `type`.
*
* This must not depend on API graphs, but ensures that an API node is generated for
* the source.
*/
DataFlow::Node getASource(string package, string type) { none() }
DataFlow::Node getASource(string type) { none() }
/**
* Gets a data-flow node that is a sink of the type `package;type`,
* Gets a data-flow node that is a sink of the given `type`,
* usually because it is an argument passed to a parameter of that type.
*
* This must not depend on API graphs, but ensures that an API node is generated for
* the sink.
*/
DataFlow::Node getASink(string package, string type) { none() }
DataFlow::Node getASink(string type) { none() }
/**
* Gets an API node that is a source or sink of the type `package;type`.
* Gets an API node that is a source or sink of the given `type`.
*
* Unlike `getASource` and `getASink`, this may depend on API graphs.
*/
API::Node getAnApiNode(string package, string type) { none() }
API::Node getAnApiNode(string type) { none() }
}
/**
@@ -209,7 +206,7 @@ private import ModelInput
/**
* An empty class, except in specific tests.
*
* If this is non-empty, all models are parsed even if the package is not
* If this is non-empty, all models are parsed even if the type name is not
* considered relevant for the current database.
*/
abstract class TestAllModels extends Unit { }
@@ -232,53 +229,44 @@ private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row
private predicate typeVariableModel(string row) { any(TypeVariableModelCsv s).row(inversePad(row)) }
/** Holds if a source model exists for the given parameters. */
predicate sourceModel(string package, string type, string path, string kind) {
predicate sourceModel(string type, string path, string kind) {
exists(string row |
sourceModel(row) and
row.splitAt(";", 0) = package and
row.splitAt(";", 1) = type and
row.splitAt(";", 2) = path and
row.splitAt(";", 3) = kind
row.splitAt(";", 0) = type and
row.splitAt(";", 1) = path and
row.splitAt(";", 2) = kind
)
}
/** Holds if a sink model exists for the given parameters. */
private predicate sinkModel(string package, string type, string path, string kind) {
private predicate sinkModel(string type, string path, string kind) {
exists(string row |
sinkModel(row) and
row.splitAt(";", 0) = package and
row.splitAt(";", 1) = type and
row.splitAt(";", 2) = path and
row.splitAt(";", 3) = kind
row.splitAt(";", 0) = type and
row.splitAt(";", 1) = path and
row.splitAt(";", 2) = kind
)
}
/** Holds if a summary model `row` exists for the given parameters. */
private predicate summaryModel(
string package, string type, string path, string input, string output, string kind
) {
private predicate summaryModel(string type, string path, string input, string output, string kind) {
exists(string row |
summaryModel(row) and
row.splitAt(";", 0) = package and
row.splitAt(";", 1) = type and
row.splitAt(";", 2) = path and
row.splitAt(";", 3) = input and
row.splitAt(";", 4) = output and
row.splitAt(";", 5) = kind
row.splitAt(";", 0) = type and
row.splitAt(";", 1) = path and
row.splitAt(";", 2) = input and
row.splitAt(";", 3) = output and
row.splitAt(";", 4) = kind
)
}
/** Holds if a type model exists for the given parameters. */
private predicate typeModel(
string package1, string type1, string package2, string type2, string path
) {
private predicate typeModel(string type1, string type2, string path) {
exists(string row |
typeModel(row) and
row.splitAt(";", 0) = package1 and
row.splitAt(";", 1) = type1 and
row.splitAt(";", 2) = package2 and
row.splitAt(";", 3) = type2 and
row.splitAt(";", 4) = path
row.splitAt(";", 0) = type1 and
row.splitAt(";", 1) = type2 and
row.splitAt(";", 2) = path
)
}
@@ -292,61 +280,50 @@ private predicate typeVariableModel(string name, string path) {
}
/**
* Gets a package that should be seen as an alias for the given other `package`,
* or the `package` itself.
* Holds if CSV rows involving `type` might be relevant for the analysis of this database.
*/
bindingset[package]
bindingset[result]
string getAPackageAlias(string package) {
typeModel(package, "", result, "", "")
or
result = package
}
/**
* Holds if CSV rows involving `package` might be relevant for the analysis of this database.
*/
private predicate isRelevantPackage(string package) {
predicate isRelevantType(string type) {
(
sourceModel(package, _, _, _) or
sinkModel(package, _, _, _) or
summaryModel(package, _, _, _, _, _) or
typeModel(_, _, package, _, _)
sourceModel(type, _, _) or
sinkModel(type, _, _) or
summaryModel(type, _, _, _, _) or
typeModel(_, type, _)
) and
(
Specific::isPackageUsed(package)
Specific::isTypeUsed(type)
or
exists(TestAllModels t)
)
or
exists(string other |
isRelevantPackage(other) and
typeModel(package, _, other, _, _)
exists(string other | isRelevantType(other) |
typeModel(type, other, _)
or
Specific::hasImplicitTypeModel(type, other)
)
}
/**
* Holds if `package,type,path` is used in some CSV row.
* Holds if `type,path` is used in some CSV row.
*/
pragma[nomagic]
predicate isRelevantFullPath(string package, string type, string path) {
isRelevantPackage(package) and
predicate isRelevantFullPath(string type, string path) {
isRelevantType(type) and
(
sourceModel(package, type, path, _) or
sinkModel(package, type, path, _) or
summaryModel(package, type, path, _, _, _) or
typeModel(_, _, package, type, path)
sourceModel(type, path, _) or
sinkModel(type, path, _) or
summaryModel(type, path, _, _, _) or
typeModel(_, type, path)
)
}
/** A string from a CSV row that should be parsed as an access path. */
private class AccessPathRange extends AccessPath::Range {
AccessPathRange() {
isRelevantFullPath(_, _, this)
isRelevantFullPath(_, this)
or
exists(string package | isRelevantPackage(package) |
summaryModel(package, _, _, this, _, _) or
summaryModel(package, _, _, _, this, _)
exists(string type | isRelevantType(type) |
summaryModel(type, _, this, _, _) or
summaryModel(type, _, _, this, _)
)
or
typeVariableModel(_, this)
@@ -400,83 +377,73 @@ private predicate invocationMatchesCallSiteFilter(Specific::InvokeNode invoke, A
}
private class TypeModelUseEntry extends API::EntryPoint {
private string package;
private string type;
TypeModelUseEntry() {
exists(any(TypeModel tm).getASource(package, type)) and
this = "TypeModelUseEntry;" + package + ";" + type
exists(any(TypeModel tm).getASource(type)) and
this = "TypeModelUseEntry;" + type
}
override DataFlow::LocalSourceNode getASource() {
result = any(TypeModel tm).getASource(package, type)
}
override DataFlow::LocalSourceNode getASource() { result = any(TypeModel tm).getASource(type) }
API::Node getNodeForType(string package_, string type_) {
package = package_ and type = type_ and result = this.getANode()
}
API::Node getNodeForType(string type_) { type = type_ and result = this.getANode() }
}
private class TypeModelDefEntry extends API::EntryPoint {
private string package;
private string type;
TypeModelDefEntry() {
exists(any(TypeModel tm).getASink(package, type)) and
this = "TypeModelDefEntry;" + package + ";" + type
exists(any(TypeModel tm).getASink(type)) and
this = "TypeModelDefEntry;" + type
}
override DataFlow::Node getASink() { result = any(TypeModel tm).getASink(package, type) }
override DataFlow::Node getASink() { result = any(TypeModel tm).getASink(type) }
API::Node getNodeForType(string package_, string type_) {
package = package_ and type = type_ and result = this.getANode()
}
API::Node getNodeForType(string type_) { type = type_ and result = this.getANode() }
}
/**
* Gets an API node identified by the given `(package,type)` pair.
* Gets an API node identified by the given `type`.
*/
pragma[nomagic]
private API::Node getNodeFromType(string package, string type) {
exists(string package2, string type2, AccessPath path2 |
typeModel(package, type, package2, type2, path2) and
result = getNodeFromPath(package2, type2, path2)
private API::Node getNodeFromType(string type) {
exists(string type2, AccessPath path2 |
typeModel(type, type2, path2) and
result = getNodeFromPath(type2, path2)
)
or
result = any(TypeModelUseEntry e).getNodeForType(package, type)
result = any(TypeModelUseEntry e).getNodeForType(type)
or
result = any(TypeModelDefEntry e).getNodeForType(package, type)
result = any(TypeModelDefEntry e).getNodeForType(type)
or
result = any(TypeModel t).getAnApiNode(package, type)
result = any(TypeModel t).getAnApiNode(type)
or
result = Specific::getExtraNodeFromType(package, type)
result = Specific::getExtraNodeFromType(type)
}
/**
* Gets the API node identified by the first `n` tokens of `path` in the given `(package, type, path)` tuple.
* Gets the API node identified by the first `n` tokens of `path` in the given `(type, path)` tuple.
*/
pragma[nomagic]
private API::Node getNodeFromPath(string package, string type, AccessPath path, int n) {
isRelevantFullPath(package, type, path) and
private API::Node getNodeFromPath(string type, AccessPath path, int n) {
isRelevantFullPath(type, path) and
(
n = 0 and
result = getNodeFromType(package, type)
result = getNodeFromType(type)
or
result = Specific::getExtraNodeFromPath(package, type, path, n)
result = Specific::getExtraNodeFromPath(type, path, n)
)
or
result = getSuccessorFromNode(getNodeFromPath(package, type, path, n - 1), path.getToken(n - 1))
result = getSuccessorFromNode(getNodeFromPath(type, path, n - 1), path.getToken(n - 1))
or
// Similar to the other recursive case, but where the path may have stepped through one or more call-site filters
result =
getSuccessorFromInvoke(getInvocationFromPath(package, type, path, n - 1), path.getToken(n - 1))
result = getSuccessorFromInvoke(getInvocationFromPath(type, path, n - 1), path.getToken(n - 1))
or
// Apply a subpath
result =
getNodeFromSubPath(getNodeFromPath(package, type, path, n - 1), getSubPathAt(path, n - 1))
result = getNodeFromSubPath(getNodeFromPath(type, path, n - 1), getSubPathAt(path, n - 1))
or
// Apply a type step
typeStep(getNodeFromPath(package, type, path, n), result)
typeStep(getNodeFromPath(type, path, n), result)
}
/**
@@ -496,15 +463,15 @@ private AccessPath getSubPathAt(AccessPath path, int n) {
pragma[nomagic]
private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath, int n) {
exists(AccessPath path, int k |
base = [getNodeFromPath(_, _, path, k), getNodeFromSubPath(_, path, k)] and
base = [getNodeFromPath(_, path, k), getNodeFromSubPath(_, path, k)] and
subPath = getSubPathAt(path, k) and
result = base and
n = 0
)
or
exists(string package, string type, AccessPath basePath |
typeStepModel(package, type, basePath, subPath) and
base = getNodeFromPath(package, type, basePath) and
exists(string type, AccessPath basePath |
typeStepModel(type, basePath, subPath) and
base = getNodeFromPath(type, basePath) and
result = base and
n = 0
)
@@ -543,42 +510,40 @@ private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath) {
result = getNodeFromSubPath(base, subPath, subPath.getNumToken())
}
/** Gets the node identified by the given `(package, type, path)` tuple. */
private API::Node getNodeFromPath(string package, string type, AccessPath path) {
result = getNodeFromPath(package, type, path, path.getNumToken())
/** Gets the node identified by the given `(type, path)` tuple. */
private API::Node getNodeFromPath(string type, AccessPath path) {
result = getNodeFromPath(type, path, path.getNumToken())
}
pragma[nomagic]
private predicate typeStepModel(string package, string type, AccessPath basePath, AccessPath output) {
summaryModel(package, type, basePath, "", output, "type")
private predicate typeStepModel(string type, AccessPath basePath, AccessPath output) {
summaryModel(type, basePath, "", output, "type")
}
pragma[nomagic]
private predicate typeStep(API::Node pred, API::Node succ) {
exists(string package, string type, AccessPath basePath, AccessPath output |
typeStepModel(package, type, basePath, output) and
pred = getNodeFromPath(package, type, basePath) and
exists(string type, AccessPath basePath, AccessPath output |
typeStepModel(type, basePath, output) and
pred = getNodeFromPath(type, basePath) and
succ = getNodeFromSubPath(pred, output)
)
}
/**
* Gets an invocation identified by the given `(package, type, path)` tuple.
* Gets an invocation identified by the given `(type, path)` tuple.
*
* Unlike `getNodeFromPath`, the `path` may end with one or more call-site filters.
*/
private Specific::InvokeNode getInvocationFromPath(
string package, string type, AccessPath path, int n
) {
result = Specific::getAnInvocationOf(getNodeFromPath(package, type, path, n))
private Specific::InvokeNode getInvocationFromPath(string type, AccessPath path, int n) {
result = Specific::getAnInvocationOf(getNodeFromPath(type, path, n))
or
result = getInvocationFromPath(package, type, path, n - 1) and
result = getInvocationFromPath(type, path, n - 1) and
invocationMatchesCallSiteFilter(result, path.getToken(n - 1))
}
/** Gets an invocation identified by the given `(package, type, path)` tuple. */
private Specific::InvokeNode getInvocationFromPath(string package, string type, AccessPath path) {
result = getInvocationFromPath(package, type, path, path.getNumToken())
/** Gets an invocation identified by the given `(type, path)` tuple. */
private Specific::InvokeNode getInvocationFromPath(string type, AccessPath path) {
result = getInvocationFromPath(type, path, path.getNumToken())
}
/**
@@ -631,9 +596,9 @@ module ModelOutput {
*/
cached
API::Node getASourceNode(string kind) {
exists(string package, string type, string path |
sourceModel(package, type, path, kind) and
result = getNodeFromPath(package, type, path)
exists(string type, string path |
sourceModel(type, path, kind) and
result = getNodeFromPath(type, path)
)
}
@@ -642,9 +607,9 @@ module ModelOutput {
*/
cached
API::Node getASinkNode(string kind) {
exists(string package, string type, string path |
sinkModel(package, type, path, kind) and
result = getNodeFromPath(package, type, path)
exists(string type, string path |
sinkModel(type, path, kind) and
result = getNodeFromPath(type, path)
)
}
@@ -653,32 +618,31 @@ module ModelOutput {
*/
cached
predicate relevantSummaryModel(
string package, string type, string path, string input, string output, string kind
string type, string path, string input, string output, string kind
) {
isRelevantPackage(package) and
summaryModel(package, type, path, input, output, kind)
isRelevantType(type) and
summaryModel(type, path, input, output, kind)
}
/**
* Holds if a `baseNode` is an invocation identified by the `package,type,path` part of a summary row.
* Holds if a `baseNode` is an invocation identified by the `type,path` part of a summary row.
*/
cached
predicate resolvedSummaryBase(
string package, string type, string path, Specific::InvokeNode baseNode
) {
summaryModel(package, type, path, _, _, _) and
baseNode = getInvocationFromPath(package, type, path)
predicate resolvedSummaryBase(string type, string path, Specific::InvokeNode baseNode) {
summaryModel(type, path, _, _, _) and
baseNode = getInvocationFromPath(type, path)
}
/**
* Holds if `node` is seen as an instance of `(package,type)` due to a type definition
* Holds if `node` is seen as an instance of `type` due to a type definition
* contributed by a CSV model.
*/
cached
API::Node getATypeNode(string package, string type) { result = getNodeFromType(package, type) }
API::Node getATypeNode(string type) { result = getNodeFromType(type) }
}
import Cached
import Specific::ModelOutputSpecific
/**
* Gets an error message relating to an invalid CSV row in a model.
@@ -686,13 +650,13 @@ module ModelOutput {
string getAWarning() {
// Check number of columns
exists(string row, string kind, int expectedArity, int actualArity |
any(SourceModelCsv csv).row(row) and kind = "source" and expectedArity = 4
any(SourceModelCsv csv).row(row) and kind = "source" and expectedArity = 3
or
any(SinkModelCsv csv).row(row) and kind = "sink" and expectedArity = 4
any(SinkModelCsv csv).row(row) and kind = "sink" and expectedArity = 3
or
any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 6
any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 5
or
any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 5
any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 3
or
any(TypeVariableModelCsv csv).row(row) and kind = "type-variable" and expectedArity = 2
|
@@ -705,7 +669,7 @@ module ModelOutput {
or
// Check names and arguments of access path tokens
exists(AccessPath path, AccessPathToken token |
(isRelevantFullPath(_, _, path) or typeVariableModel(_, path)) and
(isRelevantFullPath(_, path) or typeVariableModel(_, path)) and
token = path.getToken(_)
|
not isValidTokenNameInIdentifyingAccessPath(token.getName()) and

View File

@@ -34,30 +34,73 @@ private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSumm
private import codeql.ruby.dataflow.internal.FlowSummaryImpl::Public
private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
/**
* Holds if models describing `package` may be relevant for the analysis of this database.
*
* In the context of Ruby, this is the name of a Ruby gem.
*/
bindingset[package]
predicate isPackageUsed(string package) {
// For now everything is modeled as an access path starting at any top-level, so the package name has no effect.
//
// We allow an arbitrary package name so that the model can record the name of the package in case it's needed in the future.
//
// In principle we should consider a package to be "used" if there is a transitive dependency on it, but we can only
// reliably see the direct dependencies.
//
// In practice, packages try to use unique top-level module names, which mitigates the precision loss of not checking
// the package name.
any()
pragma[nomagic]
private predicate isUsedTopLevelConstant(string name) {
exists(ConstantAccess access |
access.getName() = name and
not exists(access.getScopeExpr())
)
}
/** Gets a Ruby-specific interpretation of the `(package, type, path)` tuple after resolving the first `n` access path tokens. */
bindingset[package, type, path]
API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int n) {
// A row of form `;any;Method[foo]` should match any method named `foo`.
exists(package) and
bindingset[rawType]
predicate isTypeUsed(string rawType) {
exists(string consts |
parseType(rawType, consts, _) and
isUsedTopLevelConstant(consts.splitAt("::", 0))
)
or
rawType = ["", "any"]
}
bindingset[rawType]
private predicate parseType(string rawType, string consts, string suffix) {
exists(string regexp |
regexp = "([^!]+)(!|)" and
consts = rawType.regexpCapture(regexp, 1) and
suffix = rawType.regexpCapture(regexp, 2)
)
}
/**
* Holds if `type` can be obtained from an instance of `otherType` due to
* language semantics modeled by `getExtraNodeFromType`.
*/
bindingset[otherType]
predicate hasImplicitTypeModel(string type, string otherType) {
// A::B! can be used to obtain A::B
parseType(otherType, type, _)
}
private predicate parseRelevantType(string rawType, string consts, string suffix) {
isRelevantType(rawType) and
parseType(rawType, consts, suffix)
}
pragma[nomagic]
private string getConstComponent(string consts, int n) {
parseRelevantType(_, consts, _) and
result = consts.splitAt("::", n)
}
private int getNumConstComponents(string consts) {
result = strictcount(int n | exists(getConstComponent(consts, n)))
}
private DataFlow::ConstRef getConstantFromConstPath(string consts, int n) {
n = 1 and
result = DataFlow::getConstant(getConstComponent(consts, 0))
or
result = getConstantFromConstPath(consts, n - 1).getConstant(getConstComponent(consts, n - 1))
}
private DataFlow::ConstRef getConstantFromConstPath(string consts) {
result = getConstantFromConstPath(consts, getNumConstComponents(consts))
}
/** Gets a Ruby-specific interpretation of the `(type, path)` tuple after resolving the first `n` access path tokens. */
bindingset[type, path]
API::Node getExtraNodeFromPath(string type, AccessPath path, int n) {
// A row of form `any;Method[foo]` should match any method named `foo`.
type = "any" and
n = 1 and
exists(EntryPointFromAnyType entry |
@@ -66,9 +109,27 @@ API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int
)
}
/** Gets a Ruby-specific interpretation of the `(package, type)` tuple. */
API::Node getExtraNodeFromType(string package, string type) {
isRelevantFullPath(package, type, _) and // Allow any package name, see `isPackageUsed`.
/** Gets a Ruby-specific interpretation of the given `type`. */
API::Node getExtraNodeFromType(string type) {
exists(string consts, string suffix, DataFlow::ConstRef constRef |
parseRelevantType(type, consts, suffix) and
constRef = getConstantFromConstPath(consts)
|
suffix = "!" and
(
result.asSource() = constRef
or
result.asSource() = constRef.getADescendentModule().getAnOwnModuleSelf()
)
or
suffix = "" and
(
result.asSource() = constRef.getAMethodCall("new")
or
result.asSource() = constRef.getADescendentModule().getAnInstanceSelf()
)
)
or
type = "" and
result = API::root()
}
@@ -78,7 +139,7 @@ API::Node getExtraNodeFromType(string package, string type) {
* matching anywhere, and the path begins with `Method[methodName]`.
*/
private predicate methodMatchedByName(AccessPath path, string methodName) {
isRelevantFullPath(_, "any", path) and
isRelevantFullPath("any", path) and
exists(AccessPathToken token |
token = path.getToken(0) and
token.getName() = "Method" and
@@ -190,3 +251,5 @@ predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string a
argument.regexpMatch("\\w+:") // keyword argument
)
}
module ModelOutputSpecific { }

View File

@@ -2,10 +2,8 @@
* Provides modeling for the `Open3` library.
*/
private import codeql.ruby.AST
private import codeql.ruby.DataFlow
private import ruby
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.Stdlib
private import codeql.ruby.Concepts
/**
@@ -17,23 +15,19 @@ module Open3 {
* These methods take the same argument forms as `Kernel.system`.
* See `KernelSystemCall` for details.
*/
class Open3Call extends SystemCommandExecution::Range {
MethodCall methodCall;
class Open3Call extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
Open3Call() {
this.asExpr().getExpr() = methodCall and
this =
API::getTopLevelMember("Open3")
.getAMethodCall(["popen3", "popen2", "popen2e", "capture3", "capture2", "capture2e"])
}
override DataFlow::Node getAnArgument() {
result.asExpr().getExpr() = methodCall.getAnArgument()
}
override DataFlow::Node getAnArgument() { result = super.getArgument(_) }
override predicate isShellInterpreted(DataFlow::Node arg) {
// These Open3 methods invoke a subshell if you provide a single string as argument
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
super.getNumberOfArguments() = 1 and
arg = this.getAnArgument()
}
}
@@ -47,11 +41,8 @@ module Open3 {
* Open3.pipeline([{}, "cat", "foo.txt"], "tail")
* Open3.pipeline([["cat", "cat"], "foo.txt"], "tail")
*/
class Open3PipelineCall extends SystemCommandExecution::Range {
MethodCall methodCall;
class Open3PipelineCall extends SystemCommandExecution::Range instanceof DataFlow::CallNode {
Open3PipelineCall() {
this.asExpr().getExpr() = methodCall and
this =
API::getTopLevelMember("Open3")
.getAMethodCall([
@@ -59,14 +50,12 @@ module Open3 {
])
}
override DataFlow::Node getAnArgument() {
result.asExpr().getExpr() = methodCall.getAnArgument()
}
override DataFlow::Node getAnArgument() { result = super.getArgument(_) }
override predicate isShellInterpreted(DataFlow::Node arg) {
// A command in the pipeline is executed in a subshell if it is given as a single string argument.
arg.asExpr().getExpr() instanceof StringlikeLiteral and
arg.asExpr().getExpr() = methodCall.getAnArgument()
arg.asExpr().getExpr() instanceof Ast::StringlikeLiteral and
arg = this.getAnArgument()
}
}
}

View File

@@ -123,33 +123,31 @@ module Pathname {
*/
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// package1;type1;package2;type2;path
// type1;type2;path
row =
[
// Pathname.new : Pathname
";Pathname;;;Member[Pathname].Instance",
// Pathname#+(path) : Pathname
";Pathname;;Pathname;Method[+].ReturnValue",
"Pathname;Pathname;Method[+].ReturnValue",
// Pathname#/(path) : Pathname
";Pathname;;Pathname;Method[/].ReturnValue",
"Pathname;Pathname;Method[/].ReturnValue",
// Pathname#basename(path) : Pathname
";Pathname;;Pathname;Method[basename].ReturnValue",
"Pathname;Pathname;Method[basename].ReturnValue",
// Pathname#cleanpath(path) : Pathname
";Pathname;;Pathname;Method[cleanpath].ReturnValue",
"Pathname;Pathname;Method[cleanpath].ReturnValue",
// Pathname#expand_path(path) : Pathname
";Pathname;;Pathname;Method[expand_path].ReturnValue",
"Pathname;Pathname;Method[expand_path].ReturnValue",
// Pathname#join(path) : Pathname
";Pathname;;Pathname;Method[join].ReturnValue",
"Pathname;Pathname;Method[join].ReturnValue",
// Pathname#realpath(path) : Pathname
";Pathname;;Pathname;Method[realpath].ReturnValue",
"Pathname;Pathname;Method[realpath].ReturnValue",
// Pathname#relative_path_from(path) : Pathname
";Pathname;;Pathname;Method[relative_path_from].ReturnValue",
"Pathname;Pathname;Method[relative_path_from].ReturnValue",
// Pathname#sub(path) : Pathname
";Pathname;;Pathname;Method[sub].ReturnValue",
"Pathname;Pathname;Method[sub].ReturnValue",
// Pathname#sub_ext(path) : Pathname
";Pathname;;Pathname;Method[sub_ext].ReturnValue",
"Pathname;Pathname;Method[sub_ext].ReturnValue",
// Pathname#to_path(path) : Pathname
";Pathname;;Pathname;Method[to_path].ReturnValue",
"Pathname;Pathname;Method[to_path].ReturnValue",
]
}
}
@@ -160,31 +158,31 @@ module Pathname {
row =
[
// Pathname.new(path)
";;Member[Pathname].Method[new];Argument[0];ReturnValue;taint",
"Pathname!;Method[new];Argument[0];ReturnValue;taint",
// Pathname#dirname
";Pathname;Method[dirname];Argument[self];ReturnValue;taint",
"Pathname;Method[dirname];Argument[self];ReturnValue;taint",
// Pathname#each_filename
";Pathname;Method[each_filename];Argument[self];Argument[block].Parameter[0];taint",
"Pathname;Method[each_filename];Argument[self];Argument[block].Parameter[0];taint",
// Pathname#expand_path
";Pathname;Method[expand_path];Argument[self];ReturnValue;taint",
"Pathname;Method[expand_path];Argument[self];ReturnValue;taint",
// Pathname#join
";Pathname;Method[join];Argument[self,any];ReturnValue;taint",
"Pathname;Method[join];Argument[self,any];ReturnValue;taint",
// Pathname#parent
";Pathname;Method[parent];Argument[self];ReturnValue;taint",
"Pathname;Method[parent];Argument[self];ReturnValue;taint",
// Pathname#realpath
";Pathname;Method[realpath];Argument[self];ReturnValue;taint",
"Pathname;Method[realpath];Argument[self];ReturnValue;taint",
// Pathname#relative_path_from
";Pathname;Method[relative_path_from];Argument[self];ReturnValue;taint",
"Pathname;Method[relative_path_from];Argument[self];ReturnValue;taint",
// Pathname#to_path
";Pathname;Method[to_path];Argument[self];ReturnValue;taint",
"Pathname;Method[to_path];Argument[self];ReturnValue;taint",
// Pathname#basename
";Pathname;Method[basename];Argument[self];ReturnValue;taint",
"Pathname;Method[basename];Argument[self];ReturnValue;taint",
// Pathname#cleanpath
";Pathname;Method[cleanpath];Argument[self];ReturnValue;taint",
"Pathname;Method[cleanpath];Argument[self];ReturnValue;taint",
// Pathname#sub
";Pathname;Method[sub];Argument[self];ReturnValue;taint",
"Pathname;Method[sub];Argument[self];ReturnValue;taint",
// Pathname#sub_ext
";Pathname;Method[sub_ext];Argument[self];ReturnValue;taint",
"Pathname;Method[sub_ext];Argument[self];ReturnValue;taint",
]
}
}

View File

@@ -70,7 +70,7 @@ class PrintAstNode extends TPrintNode {
* Holds if this node is at the specified location. The location spans column
* `startcolumn` of line `startline` to column `endcolumn` of line `endline`
* in file `filepath`. For more information, see
* [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn

File diff suppressed because it is too large Load Diff

View File

@@ -2,155 +2,7 @@
* Provides predicates for reasoning about bad tag filter vulnerabilities.
*/
import regexp.RegexpMatching
/**
* Holds if the regexp `root` should be tested against `str`.
* Implements the `isRegexpMatchingCandidateSig` signature from `RegexpMatching`.
* `ignorePrefix` toggles whether the regular expression should be treated as accepting any prefix if it's unanchored.
* `testWithGroups` toggles whether it's tested which groups are filled by a given input string.
*/
private predicate isBadTagFilterCandidate(
RootTerm root, string str, boolean ignorePrefix, boolean testWithGroups
) {
// the regexp must mention "<" and ">" explicitly.
forall(string angleBracket | angleBracket = ["<", ">"] |
any(RegExpConstant term | term.getValue().matches("%" + angleBracket + "%")).getRootTerm() =
root
) and
ignorePrefix = true and
(
str = ["<!-- foo -->", "<!-- foo --!>", "<!- foo ->", "<foo>", "<script>"] and
testWithGroups = true
or
str =
[
"<!-- foo -->", "<!- foo ->", "<!-- foo --!>", "<!-- foo\n -->", "<script>foo</script>",
"<script \n>foo</script>", "<script >foo\n</script>", "<foo ></foo>", "<foo>",
"<foo src=\"foo\"></foo>", "<script>", "<script src=\"foo\"></script>",
"<script src='foo'></script>", "<SCRIPT>foo</SCRIPT>", "<script\tsrc=\"foo\"/>",
"<script\tsrc='foo'></script>", "<sCrIpT>foo</ScRiPt>", "<script src=\"foo\">foo</script >",
"<script src=\"foo\">foo</script foo=\"bar\">", "<script src=\"foo\">foo</script\t\n bar>"
] and
testWithGroups = false
)
}
/**
* A regexp that matches some string from the `isBadTagFilterCandidate` predicate.
*/
class HtmlMatchingRegExp extends RootTerm {
HtmlMatchingRegExp() { RegexpMatching<isBadTagFilterCandidate/4>::matches(this, _) }
/** Holds if this regexp matched `str`, where `str` is one of the string from `isBadTagFilterCandidate`. */
predicate matches(string str) { RegexpMatching<isBadTagFilterCandidate/4>::matches(this, str) }
/** Holds if this regexp fills capture group `g' when matching `str', where `str` is one of the string from `isBadTagFilterCandidate`. */
predicate fillsCaptureGroup(string str, int g) {
RegexpMatching<isBadTagFilterCandidate/4>::fillsCaptureGroup(this, str, g)
}
}
/** DEPRECATED: Alias for HtmlMatchingRegExp */
deprecated class HTMLMatchingRegExp = HtmlMatchingRegExp;
/**
* Holds if `regexp` matches some HTML tags, but misses some HTML tags that it should match.
*
* When adding a new case to this predicate, make sure the test string used in `matches(..)` calls are present in `HTMLMatchingRegExp::test` / `HTMLMatchingRegExp::testWithGroups`.
*/
predicate isBadRegexpFilter(HtmlMatchingRegExp regexp, string msg) {
// CVE-2021-33829 - matching both "<!-- foo -->" and "<!-- foo --!>", but in different capture groups
regexp.matches("<!-- foo -->") and
regexp.matches("<!-- foo --!>") and
exists(int a, int b | a != b |
regexp.fillsCaptureGroup("<!-- foo -->", a) and
// <!-- foo --> might be ambiguously parsed (matching both capture groups), and that is ok here.
regexp.fillsCaptureGroup("<!-- foo --!>", b) and
not regexp.fillsCaptureGroup("<!-- foo --!>", a) and
msg =
"Comments ending with --> are matched differently from comments ending with --!>. The first is matched with capture group "
+ a + " and comments ending with --!> are matched with capture group " +
strictconcat(int i | regexp.fillsCaptureGroup("<!-- foo --!>", i) | i.toString(), ", ") +
"."
)
or
// CVE-2020-17480 - matching "<!-- foo -->" and other tags, but not "<!-- foo --!>".
exists(int group, int other |
group != other and
regexp.fillsCaptureGroup("<!-- foo -->", group) and
regexp.fillsCaptureGroup("<foo>", other) and
not regexp.matches("<!-- foo --!>") and
not regexp.fillsCaptureGroup("<!-- foo -->", any(int i | i != group)) and
not regexp.fillsCaptureGroup("<!- foo ->", group) and
not regexp.fillsCaptureGroup("<foo>", group) and
not regexp.fillsCaptureGroup("<script>", group) and
msg =
"This regular expression only parses --> (capture group " + group +
") and not --!> as an HTML comment end tag."
)
or
regexp.matches("<!-- foo -->") and
not regexp.matches("<!-- foo\n -->") and
not regexp.matches("<!- foo ->") and
not regexp.matches("<foo>") and
not regexp.matches("<script>") and
msg = "This regular expression does not match comments containing newlines."
or
regexp.matches("<script>foo</script>") and
regexp.matches("<script src=\"foo\"></script>") and
not regexp.matches("<foo ></foo>") and
(
not regexp.matches("<script \n>foo</script>") and
msg = "This regular expression matches <script></script>, but not <script \\n></script>"
or
not regexp.matches("<script >foo\n</script>") and
msg = "This regular expression matches <script>...</script>, but not <script >...\\n</script>"
)
or
regexp.matches("<script>foo</script>") and
regexp.matches("<script src=\"foo\"></script>") and
not regexp.matches("<script src='foo'></script>") and
not regexp.matches("<foo>") and
msg = "This regular expression does not match script tags where the attribute uses single-quotes."
or
regexp.matches("<script>foo</script>") and
regexp.matches("<script src='foo'></script>") and
not regexp.matches("<script src=\"foo\"></script>") and
not regexp.matches("<foo>") and
msg = "This regular expression does not match script tags where the attribute uses double-quotes."
or
regexp.matches("<script>foo</script>") and
regexp.matches("<script src='foo'></script>") and
not regexp.matches("<script\tsrc='foo'></script>") and
not regexp.matches("<foo>") and
not regexp.matches("<foo src=\"foo\"></foo>") and
msg = "This regular expression does not match script tags where tabs are used between attributes."
or
regexp.matches("<script>foo</script>") and
not RegExpFlags::isIgnoreCase(regexp) and
not regexp.matches("<foo>") and
not regexp.matches("<foo ></foo>") and
(
not regexp.matches("<SCRIPT>foo</SCRIPT>") and
msg = "This regular expression does not match upper case <SCRIPT> tags."
or
not regexp.matches("<sCrIpT>foo</ScRiPt>") and
regexp.matches("<SCRIPT>foo</SCRIPT>") and
msg = "This regular expression does not match mixed case <sCrIpT> tags."
)
or
regexp.matches("<script src=\"foo\"></script>") and
not regexp.matches("<foo>") and
not regexp.matches("<foo ></foo>") and
(
not regexp.matches("<script src=\"foo\">foo</script >") and
msg = "This regular expression does not match script end tags like </script >."
or
not regexp.matches("<script src=\"foo\">foo</script foo=\"bar\">") and
msg = "This regular expression does not match script end tags like </script foo=\"bar\">."
or
not regexp.matches("<script src=\"foo\">foo</script\t\n bar>") and
msg = "This regular expression does not match script end tags like </script\\t\\n bar>."
)
}
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
// BadTagFilterQuery should be used directly from the shared pack, and not from this file.
deprecated import codeql.regex.nfa.BadTagFilterQuery::Make<TreeView> as Dep
import Dep

View File

@@ -3,8 +3,9 @@
*/
import codeql.ruby.frameworks.core.String
import codeql.ruby.regexp.RegExpTreeView
import codeql.ruby.security.regexp.NfaUtils as NfaUtils
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
import TreeView
import codeql.regex.nfa.NfaUtils::Make<TreeView> as NfaUtils
/**
* A regexp term that matches substrings that should be replaced with the empty string.

View File

@@ -60,6 +60,14 @@ class StringReplaceSanitizer extends Sanitizer {
}
}
/**
* A call to `Object#inspect`, considered as a sanitizer.
* This is because `inspect` will replace newlines in strings with `\n`.
*/
class InspectSanitizer extends Sanitizer {
InspectSanitizer() { this.(DataFlow::CallNode).getMethodName() = "inspect" }
}
/**
* A call to an HTML escape method is considered to sanitize its input.
*/

View File

@@ -2,288 +2,7 @@
* Classes and predicates for working with suspicious character ranges.
*/
// We don't need the NFA utils, just the regexp tree.
// but the below is a nice shared library that exposes the API we need.
import regexp.NfaUtils
/**
* Gets a rank for `range` that is unique for ranges in the same file.
* Prioritizes ranges that match more characters.
*/
int rankRange(RegExpCharacterRange range) {
range =
rank[result](RegExpCharacterRange r, Location l, int low, int high |
r.getLocation() = l and
isRange(r, low, high)
|
r order by (high - low) desc, l.getStartLine(), l.getStartColumn()
)
}
/** Holds if `range` spans from the unicode code points `low` to `high` (both inclusive). */
predicate isRange(RegExpCharacterRange range, int low, int high) {
exists(string lowc, string highc |
range.isRange(lowc, highc) and
low.toUnicode() = lowc and
high.toUnicode() = highc
)
}
/** Holds if `char` is an alpha-numeric character. */
predicate isAlphanumeric(string char) {
// written like this to avoid having a bindingset for the predicate
char = [[48 .. 57], [65 .. 90], [97 .. 122]].toUnicode() // 0-9, A-Z, a-z
}
/**
* Holds if the given ranges are from the same character class
* and there exists at least one character matched by both ranges.
*/
predicate overlap(RegExpCharacterRange a, RegExpCharacterRange b) {
exists(RegExpCharacterClass clz |
a = clz.getAChild() and
b = clz.getAChild() and
a != b
|
exists(int alow, int ahigh, int blow, int bhigh |
isRange(a, alow, ahigh) and
isRange(b, blow, bhigh) and
alow <= bhigh and
blow <= ahigh
)
)
}
/**
* Holds if `range` overlaps with the char class `escape` from the same character class.
*/
predicate overlapsWithCharEscape(RegExpCharacterRange range, RegExpCharacterClassEscape escape) {
exists(RegExpCharacterClass clz, string low, string high |
range = clz.getAChild() and
escape = clz.getAChild() and
range.isRange(low, high)
|
escape.getValue() = "w" and
getInRange(low, high).regexpMatch("\\w")
or
escape.getValue() = "d" and
getInRange(low, high).regexpMatch("\\d")
or
escape.getValue() = "s" and
getInRange(low, high).regexpMatch("\\s")
)
}
/** Gets the unicode code point for a `char`. */
bindingset[char]
int toCodePoint(string char) { result.toUnicode() = char }
/** A character range that appears to be overly wide. */
class OverlyWideRange extends RegExpCharacterRange {
OverlyWideRange() {
exists(int low, int high, int numChars |
isRange(this, low, high) and
numChars = (1 + high - low) and
this.getRootTerm().isUsedAsRegExp() and
numChars >= 10
|
// across the Z-a range (which includes backticks)
toCodePoint("Z") >= low and
toCodePoint("a") <= high
or
// across the 9-A range (which includes e.g. ; and ?)
toCodePoint("9") >= low and
toCodePoint("A") <= high
or
// a non-alphanumeric char as part of the range boundaries
exists(int bound | bound = [low, high] | not isAlphanumeric(bound.toUnicode())) and
// while still being ascii
low < 128 and
high < 128
) and
// allowlist for known ranges
not this = allowedWideRanges()
}
/** Gets a string representation of a character class that matches the same chars as this range. */
string printEquivalent() { result = RangePrinter::printEquivalentCharClass(this) }
}
/** Gets a range that should not be reported as an overly wide range. */
RegExpCharacterRange allowedWideRanges() {
// ~ is the last printable ASCII character, it's used right in various wide ranges.
result.isRange(_, "~")
or
// the same with " " and "!". " " is the first printable character, and "!" is the first non-white-space printable character.
result.isRange([" ", "!"], _)
or
// the `[@-_]` range is intentional
result.isRange("@", "_")
or
// starting from the zero byte is a good indication that it's purposely matching a large range.
result.isRange(0.toUnicode(), _)
}
/** Gets a char between (and including) `low` and `high`. */
bindingset[low, high]
private string getInRange(string low, string high) {
result = [toCodePoint(low) .. toCodePoint(high)].toUnicode()
}
/** A module computing an equivalent character class for an overly wide range. */
module RangePrinter {
bindingset[char]
bindingset[result]
private string next(string char) {
exists(int prev, int next |
prev.toUnicode() = char and
next.toUnicode() = result and
next = prev + 1
)
}
/** Gets the points where the parts of the pretty printed range should be cut off. */
private string cutoffs() { result = ["A", "Z", "a", "z", "0", "9"] }
/** Gets the char to use in the low end of a range for a given `cut` */
private string lowCut(string cut) {
cut = ["A", "a", "0"] and
result = cut
or
cut = ["Z", "z", "9"] and
result = next(cut)
}
/** Gets the char to use in the high end of a range for a given `cut` */
private string highCut(string cut) {
cut = ["Z", "z", "9"] and
result = cut
or
cut = ["A", "a", "0"] and
next(result) = cut
}
/** Gets the cutoff char used for a given `part` of a range when pretty-printing it. */
private string cutoff(OverlyWideRange range, int part) {
exists(int low, int high | isRange(range, low, high) |
result =
rank[part + 1](string cut |
cut = cutoffs() and low < toCodePoint(cut) and toCodePoint(cut) < high
|
cut order by toCodePoint(cut)
)
)
}
/** Gets the number of parts we should print for a given `range`. */
private int parts(OverlyWideRange range) { result = 1 + count(cutoff(range, _)) }
/** Holds if the given part of a range should span from `low` to `high`. */
private predicate part(OverlyWideRange range, int part, string low, string high) {
// first part.
part = 0 and
(
range.isRange(low, high) and
parts(range) = 1
or
parts(range) >= 2 and
range.isRange(low, _) and
high = highCut(cutoff(range, part))
)
or
// middle
part >= 1 and
part < parts(range) - 1 and
low = lowCut(cutoff(range, part - 1)) and
high = highCut(cutoff(range, part))
or
// last.
part = parts(range) - 1 and
low = lowCut(cutoff(range, part - 1)) and
range.isRange(_, high)
}
/** Gets an escaped `char` for use in a character class. */
bindingset[char]
private string escape(string char) {
exists(string reg | reg = "(\\[|\\]|\\\\|-|/)" |
if char.regexpMatch(reg) then result = "\\" + char else result = char
)
}
/** Gets a part of the equivalent range. */
private string printEquivalentCharClass(OverlyWideRange range, int part) {
exists(string low, string high | part(range, part, low, high) |
if
isAlphanumeric(low) and
isAlphanumeric(high)
then result = low + "-" + high
else
result =
strictconcat(string char | char = getInRange(low, high) | escape(char) order by char)
)
}
/** Gets the entire pretty printed equivalent range. */
string printEquivalentCharClass(OverlyWideRange range) {
result =
strictconcat(string r, int part |
r = "[" and part = -1 and exists(range)
or
r = printEquivalentCharClass(range, part)
or
r = "]" and part = parts(range)
|
r order by part
)
}
}
/** Gets a char range that is overly large because of `reason`. */
RegExpCharacterRange getABadRange(string reason, int priority) {
result instanceof OverlyWideRange and
priority = 0 and
exists(string equiv | equiv = result.(OverlyWideRange).printEquivalent() |
if equiv.length() <= 50
then reason = "is equivalent to " + equiv
else reason = "is equivalent to " + equiv.substring(0, 50) + "..."
)
or
priority = 1 and
exists(RegExpCharacterRange other |
reason = "overlaps with " + other + " in the same character class" and
rankRange(result) < rankRange(other) and
overlap(result, other)
)
or
priority = 2 and
exists(RegExpCharacterClassEscape escape |
reason = "overlaps with " + escape + " in the same character class" and
overlapsWithCharEscape(result, escape)
)
or
reason = "is empty" and
priority = 3 and
exists(int low, int high |
isRange(result, low, high) and
low > high
)
}
/** Holds if `range` matches suspiciously many characters. */
predicate problem(RegExpCharacterRange range, string reason) {
reason =
strictconcat(string m, int priority |
range = getABadRange(m, priority)
|
m, ", and " order by priority desc
) and
// specifying a range using an escape is usually OK.
not range.getAChild() instanceof RegExpEscape and
// Unicode escapes in strings are interpreted before it turns into a regexp,
// so e.g. [\u0001-\uFFFF] will just turn up as a range between two constants.
// We therefore exclude these ranges.
range.getRootTerm().getParent() instanceof RegExpLiteral and
// is used as regexp (mostly for JS where regular expressions are parsed eagerly)
range.getRootTerm().isUsedAsRegExp()
}
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
// OverlyLargeRangeQuery should be used directly from the shared pack, and not from this file.
deprecated import codeql.regex.OverlyLargeRangeQuery::Make<TreeView> as Dep
import Dep

View File

@@ -0,0 +1,55 @@
/**
* Provides default sources, sinks and sanitizers for detecting SQL injection
* vulnerabilities, as well as extension points for adding your own.
*/
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.BarrierGuards
private import codeql.ruby.dataflow.RemoteFlowSources
/**
* Provides default sources, sinks and sanitizers for detecting SQL injection
* vulnerabilities, as well as extension points for adding your own.
*/
module SqlInjection {
/** A data flow source for SQL injection vulnerabilities. */
abstract class Source extends DataFlow::Node { }
/** A data flow sink for SQL injection vulnerabilities. */
abstract class Sink extends DataFlow::Node { }
/** A sanitizer for SQL injection vulnerabilities. */
abstract class Sanitizer extends DataFlow::Node { }
/**
* A source of remote user input, considered as a flow source.
*/
private class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
/**
* An SQL statement of a SQL execution, considered as a flow sink.
*/
private class SqlExecutionAsSink extends Sink {
SqlExecutionAsSink() { this = any(SqlExecution e).getSql() }
}
/**
* An SQL statement of a SQL construction, considered as a flow sink.
*/
private class SqlConstructionAsSink extends Sink {
SqlConstructionAsSink() { this = any(SqlConstruction e).getSql() }
}
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/
private class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
/**
* An inclusion check against an array of constant strings, considered as a
* sanitizer-guard.
*/
class StringConstArrayInclusionCallAsSanitizer extends Sanitizer,
StringConstArrayInclusionCallBarrier { }
}

View File

@@ -0,0 +1,21 @@
/**
* Provides default sources, sinks and sanitizers for detecting SQL injection
* vulnerabilities, as well as extension points for adding your own.
*/
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
import SqlInjectionCustomizations::SqlInjection
/**
* A taint-tracking configuration for detecting SQL injection vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "SqlInjectionConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node source) { source instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
}

View File

@@ -0,0 +1,55 @@
/**
* Provides default sources, sinks and sanitizers for detecting stack trace
* exposure vulnerabilities, as well as extension points for adding your own.
*/
private import codeql.ruby.AST
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.frameworks.core.Kernel
/**
* Provides default sources, sinks and sanitizers for detecting stack trace
* exposure vulnerabilities, as well as extension points for adding your own.
*/
module StackTraceExposure {
/** A data flow source for stack trace exposure vulnerabilities. */
abstract class Source extends DataFlow::Node { }
/** A data flow sink for stack trace exposure vulnerabilities. */
abstract class Sink extends DataFlow::Node { }
/** A data flow sanitizer for stack trace exposure vulnerabilities. */
abstract class Sanitizer extends DataFlow::Node { }
/**
* A call to `backtrace` or `backtrace_locations` on a `rescue` variable,
* considered as a flow source.
*/
class BacktraceCall extends Source, DataFlow::CallNode {
BacktraceCall() {
exists(DataFlow::LocalSourceNode varAccess |
varAccess.asExpr().(ExprNodes::VariableReadAccessCfgNode).getExpr().getVariable() =
any(RescueClause rc).getVariableExpr().(VariableAccess).getVariable() and
varAccess.flowsTo(this.getReceiver())
) and
this.getMethodName() = ["backtrace", "backtrace_locations"]
}
}
/**
* A call to `Kernel#caller`, considered as a flow source.
*/
class KernelCallerCall extends Source, Kernel::KernelMethodCall {
KernelCallerCall() { this.getMethodName() = "caller" }
}
/**
* The body of an HTTP response that will be returned from a server,
* considered as a flow sink.
*/
class ServerHttpResponseBodyAsSink extends Sink {
ServerHttpResponseBodyAsSink() { this = any(Http::Server::HttpResponse response).getBody() }
}
}

View File

@@ -0,0 +1,25 @@
/**
* Provides a taint-tracking configuration for detecting stack-trace exposure
* vulnerabilities.
*
* Note, for performance reasons: only import this file if
* `StackTraceExposure::Configuration` is needed; otherwise,
* `StackTraceExposureCustomizations` should be imported instead.
*/
private import codeql.ruby.DataFlow
private import codeql.ruby.TaintTracking
private import StackTraceExposureCustomizations::StackTraceExposure
/**
* A taint-tracking configuration for detecting "stack trace exposure" vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "StackTraceExposure" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
}

View File

@@ -2,73 +2,8 @@
* Provides Ruby-specific imports and classes needed for `TaintedFormatStringQuery` and `TaintedFormatStringCustomizations`.
*/
import codeql.ruby.AST
import codeql.ruby.frameworks.StringFormatters
import codeql.ruby.DataFlow
import codeql.ruby.dataflow.RemoteFlowSources
import codeql.ruby.ApiGraphs
import codeql.ruby.TaintTracking
private import codeql.ruby.frameworks.Files
private import codeql.ruby.frameworks.core.IO
private import codeql.ruby.controlflow.CfgNodes
/**
* A call to `printf` or `sprintf`.
*/
abstract class PrintfStyleCall extends DataFlow::CallNode {
// We assume that most printf-like calls have the signature f(format_string, args...)
/**
* Gets the format string of this call.
*/
DataFlow::Node getFormatString() { result = this.getArgument(0) }
/**
* Gets then `n`th formatted argument of this call.
*/
DataFlow::Node getFormatArgument(int n) { n >= 0 and result = this.getArgument(n + 1) }
}
/**
* A call to `Kernel.printf`.
*/
class KernelPrintfCall extends PrintfStyleCall {
KernelPrintfCall() {
this = API::getTopLevelMember("Kernel").getAMethodCall("printf")
or
this.asExpr().getExpr() instanceof UnknownMethodCall and
this.getMethodName() = "printf"
}
// Kernel#printf supports two signatures:
// printf(io, string, ...)
// printf(string, ...)
override DataFlow::Node getFormatString() {
// Because `printf` has two different signatures, we can't be sure which
// argument is the format string, so we use a heuristic:
// If the first argument has a string value, then we assume it is the format string.
// Otherwise we treat both the first and second args as the format string.
if this.getArgument(0).getExprNode().getConstantValue().isString(_)
then result = this.getArgument(0)
else result = this.getArgument([0, 1])
}
}
/**
* A call to `Kernel.sprintf`.
*/
class KernelSprintfCall extends PrintfStyleCall {
KernelSprintfCall() {
this = API::getTopLevelMember("Kernel").getAMethodCall("sprintf")
or
this.asExpr().getExpr() instanceof UnknownMethodCall and
this.getMethodName() = "sprintf"
}
}
/**
* A call to `IO#printf`.
*/
class IOPrintfCall extends PrintfStyleCall {
IOPrintfCall() {
this.getReceiver() instanceof IO::IOInstance and this.getMethodName() = "printf"
}
}
import codeql.ruby.DataFlow

View File

@@ -62,284 +62,7 @@
* a suffix `x` (possible empty) that is most likely __not__ accepted.
*/
import NfaUtils
/**
* Holds if state `s` might be inside a backtracking repetition.
*/
pragma[noinline]
private predicate stateInsideBacktracking(State s) {
s.getRepr().getParent*() instanceof MaybeBacktrackingRepetition
}
/**
* A infinitely repeating quantifier that might backtrack.
*/
private class MaybeBacktrackingRepetition extends InfiniteRepetitionQuantifier {
MaybeBacktrackingRepetition() {
exists(RegExpTerm child |
child instanceof RegExpAlt or
child instanceof RegExpQuantifier
|
child.getParent+() = this
)
}
}
/**
* A state in the product automaton.
*/
private newtype TStatePair =
/**
* We lazily only construct those states that we are actually
* going to need: `(q, q)` for every fork state `q`, and any
* pair of states that can be reached from a pair that we have
* already constructed. To cut down on the number of states,
* we only represent states `(q1, q2)` where `q1` is lexicographically
* no bigger than `q2`.
*
* States are only constructed if both states in the pair are
* inside a repetition that might backtrack.
*/
MkStatePair(State q1, State q2) {
isFork(q1, _, _, _, _) and q2 = q1
or
(step(_, _, _, q1, q2) or step(_, _, _, q2, q1)) and
rankState(q1) <= rankState(q2)
}
/**
* Gets a unique number for a `state`.
* Is used to create an ordering of states, where states with the same `toString()` will be ordered differently.
*/
private int rankState(State state) {
state =
rank[result](State s, Location l |
stateInsideBacktracking(s) and
l = s.getRepr().getLocation()
|
s order by l.getStartLine(), l.getStartColumn(), s.toString()
)
}
/**
* A state in the product automaton.
*/
private class StatePair extends TStatePair {
State q1;
State q2;
StatePair() { this = MkStatePair(q1, q2) }
/** Gets a textual representation of this element. */
string toString() { result = "(" + q1 + ", " + q2 + ")" }
/** Gets the first component of the state pair. */
State getLeft() { result = q1 }
/** Gets the second component of the state pair. */
State getRight() { result = q2 }
}
/**
* Holds for `(fork, fork)` state pairs when `isFork(fork, _, _, _, _)` holds.
*
* Used in `statePairDistToFork`
*/
private predicate isStatePairFork(StatePair p) {
exists(State fork | p = MkStatePair(fork, fork) and isFork(fork, _, _, _, _))
}
/**
* Holds if there are transitions from the components of `q` to the corresponding
* components of `r`.
*
* Used in `statePairDistToFork`
*/
private predicate reverseStep(StatePair r, StatePair q) { step(q, _, _, r) }
/**
* Gets the minimum length of a path from `q` to `r` in the
* product automaton.
*/
private int statePairDistToFork(StatePair q, StatePair r) =
shortestDistances(isStatePairFork/1, reverseStep/2)(r, q, result)
/**
* Holds if there are transitions from `q` to `r1` and from `q` to `r2`
* labelled with `s1` and `s2`, respectively, where `s1` and `s2` do not
* trivially have an empty intersection.
*
* This predicate only holds for states associated with regular expressions
* that have at least one repetition quantifier in them (otherwise the
* expression cannot be vulnerable to ReDoS attacks anyway).
*/
pragma[noopt]
private predicate isFork(State q, InputSymbol s1, InputSymbol s2, State r1, State r2) {
stateInsideBacktracking(q) and
exists(State q1, State q2 |
q1 = epsilonSucc*(q) and
delta(q1, s1, r1) and
q2 = epsilonSucc*(q) and
delta(q2, s2, r2) and
// Use pragma[noopt] to prevent intersect(s1,s2) from being the starting point of the join.
// From (s1,s2) it would find a huge number of intermediate state pairs (q1,q2) originating from different literals,
// and discover at the end that no `q` can reach both `q1` and `q2` by epsilon transitions.
exists(intersect(s1, s2))
|
s1 != s2
or
r1 != r2
or
r1 = r2 and q1 != q2
or
// If q can reach itself by epsilon transitions, then there are two distinct paths to the q1/q2 state:
// one that uses the loop and one that doesn't. The engine will separately attempt to match with each path,
// despite ending in the same state. The "fork" thus arises from the choice of whether to use the loop or not.
// To avoid every state in the loop becoming a fork state,
// we arbitrarily pick the InfiniteRepetitionQuantifier state as the canonical fork state for the loop
// (every epsilon-loop must contain such a state).
//
// We additionally require that the there exists another InfiniteRepetitionQuantifier `mid` on the path from `q` to itself.
// This is done to avoid flagging regular expressions such as `/(a?)*b/` - that only has polynomial runtime, and is detected by `js/polynomial-redos`.
// The below code is therefore a heuristic, that only flags regular expressions such as `/(a*)*b/`,
// and does not flag regular expressions such as `/(a?b?)c/`, but the latter pattern is not used frequently.
r1 = r2 and
q1 = q2 and
epsilonSucc+(q) = q and
exists(RegExpTerm term | term = q.getRepr() | term instanceof InfiniteRepetitionQuantifier) and
// One of the mid states is an infinite quantifier itself
exists(State mid, RegExpTerm term |
mid = epsilonSucc+(q) and
term = mid.getRepr() and
term instanceof InfiniteRepetitionQuantifier and
q = epsilonSucc+(mid) and
not mid = q
)
) and
stateInsideBacktracking(r1) and
stateInsideBacktracking(r2)
}
/**
* Gets the state pair `(q1, q2)` or `(q2, q1)`; note that only
* one or the other is defined.
*/
private StatePair mkStatePair(State q1, State q2) {
result = MkStatePair(q1, q2) or result = MkStatePair(q2, q1)
}
/**
* Holds if there are transitions from the components of `q` to the corresponding
* components of `r` labelled with `s1` and `s2`, respectively.
*/
private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, StatePair r) {
exists(State r1, State r2 | step(q, s1, s2, r1, r2) and r = mkStatePair(r1, r2))
}
/**
* Holds if there are transitions from the components of `q` to `r1` and `r2`
* labelled with `s1` and `s2`, respectively.
*
* We only consider transitions where the resulting states `(r1, r2)` are both
* inside a repetition that might backtrack.
*/
pragma[noopt]
private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, State r1, State r2) {
exists(State q1, State q2 | q.getLeft() = q1 and q.getRight() = q2 |
deltaClosed(q1, s1, r1) and
deltaClosed(q2, s2, r2) and
// use noopt to force the join on `intersect` to happen last.
exists(intersect(s1, s2))
) and
stateInsideBacktracking(r1) and
stateInsideBacktracking(r2)
}
private newtype TTrace =
Nil() or
Step(InputSymbol s1, InputSymbol s2, TTrace t) { isReachableFromFork(_, _, s1, s2, t, _) }
/**
* A list of pairs of input symbols that describe a path in the product automaton
* starting from some fork state.
*/
private class Trace extends TTrace {
/** Gets a textual representation of this element. */
string toString() {
this = Nil() and result = "Nil()"
or
exists(InputSymbol s1, InputSymbol s2, Trace t | this = Step(s1, s2, t) |
result = "Step(" + s1 + ", " + s2 + ", " + t + ")"
)
}
}
/**
* Holds if `r` is reachable from `(fork, fork)` under input `w`, and there is
* a path from `r` back to `(fork, fork)` with `rem` steps.
*/
private predicate isReachableFromFork(State fork, StatePair r, Trace w, int rem) {
exists(InputSymbol s1, InputSymbol s2, Trace v |
isReachableFromFork(fork, r, s1, s2, v, rem) and
w = Step(s1, s2, v)
)
}
private predicate isReachableFromFork(
State fork, StatePair r, InputSymbol s1, InputSymbol s2, Trace v, int rem
) {
// base case
exists(State q1, State q2 |
isFork(fork, s1, s2, q1, q2) and
r = MkStatePair(q1, q2) and
v = Nil() and
rem = statePairDistToFork(r, MkStatePair(fork, fork))
)
or
// recursive case
exists(StatePair p |
isReachableFromFork(fork, p, v, rem + 1) and
step(p, s1, s2, r) and
rem = statePairDistToFork(r, MkStatePair(fork, fork))
)
}
/**
* Gets a state in the product automaton from which `(fork, fork)` is
* reachable in zero or more epsilon transitions.
*/
private StatePair getAForkPair(State fork) {
isFork(fork, _, _, _, _) and
result = MkStatePair(epsilonPred*(fork), epsilonPred*(fork))
}
/** An implementation of a chain containing chars for use by `Concretizer`. */
private module CharTreeImpl implements CharTree {
class CharNode = Trace;
CharNode getPrev(CharNode t) { t = Step(_, _, result) }
/** Holds if `n` is a trace that is used by `concretize` in `isPumpable`. */
predicate isARelevantEnd(CharNode n) {
exists(State f | isReachableFromFork(f, getAForkPair(f), n, _))
}
string getChar(CharNode t) {
exists(InputSymbol s1, InputSymbol s2 | t = Step(s1, s2, _) | result = intersect(s1, s2))
}
}
/**
* Holds if `fork` is a pumpable fork with word `w`.
*/
private predicate isPumpable(State fork, string w) {
exists(StatePair q, Trace t |
isReachableFromFork(fork, q, t, _) and
q = getAForkPair(fork) and
w = Concretizer<CharTreeImpl>::concretize(t)
)
}
/** Holds if `state` has exponential ReDoS */
predicate hasReDoSResult = ReDoSPruning<isPumpable/2>::hasReDoSResult/4;
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
// ExponentialBackTracking should be used directly from the shared pack, and not from this file.
deprecated private import codeql.regex.nfa.ExponentialBackTracking::Make<TreeView> as Dep
import Dep

File diff suppressed because it is too large Load Diff

View File

@@ -1,73 +0,0 @@
/**
* Provides Ruby-specific definitions for use in the NfaUtils module.
*/
import codeql.ruby.Regexp
import codeql.Locations
private import codeql.ruby.ast.Literal as Ast
/**
* Holds if `term` is an escape class representing e.g. `\d`.
* `clazz` is which character class it represents, e.g. "d" for `\d`.
*/
predicate isEscapeClass(RegExpTerm term, string clazz) {
exists(RegExpCharacterClassEscape escape | term = escape | escape.getValue() = clazz)
or
// TODO: expand to cover more properties
exists(RegExpNamedCharacterProperty escape | term = escape |
escape.getName().toLowerCase() = "digit" and
if escape.isInverted() then clazz = "D" else clazz = "d"
or
escape.getName().toLowerCase() = "space" and
if escape.isInverted() then clazz = "S" else clazz = "s"
or
escape.getName().toLowerCase() = "word" and
if escape.isInverted() then clazz = "W" else clazz = "w"
)
}
/**
* Holds if the regular expression should not be considered.
*/
predicate isExcluded(RegExpParent parent) {
parent.(RegExpTerm).getRegExp().(Ast::RegExpLiteral).hasFreeSpacingFlag() // exclude free-spacing mode regexes
}
/**
* Holds if `term` is a possessive quantifier.
* Not currently implemented, but is used by the shared library.
*/
predicate isPossessive(RegExpQuantifier term) { none() }
/**
* Holds if the regex that `term` is part of is used in a way that ignores any leading prefix of the input it's matched against.
* Not yet implemented for Ruby.
*/
predicate matchesAnyPrefix(RegExpTerm term) { any() }
/**
* Holds if the regex that `term` is part of is used in a way that ignores any trailing suffix of the input it's matched against.
* Not yet implemented for Ruby.
*/
predicate matchesAnySuffix(RegExpTerm term) { any() }
/**
* A module containing predicates for determining which flags a regular expression have.
*/
module RegExpFlags {
/**
* Holds if `root` has the `i` flag for case-insensitive matching.
*/
predicate isIgnoreCase(RegExpTerm root) {
root.isRootTerm() and
root.getLiteral().isIgnoreCase()
}
/**
* Holds if `root` has the `s` flag for multi-line matching.
*/
predicate isDotAll(RegExpTerm root) {
root.isRootTerm() and
root.getLiteral().isDotAll()
}
}

View File

@@ -8,8 +8,7 @@ private import codeql.ruby.AST as Ast
private import codeql.ruby.CFG
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.Regexp
private import codeql.ruby.security.regexp.SuperlinearBackTracking
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
/**
* Provides default sources, sinks and sanitizers for reasoning about
@@ -17,6 +16,9 @@ private import codeql.ruby.security.regexp.SuperlinearBackTracking
* as extension points for adding your own.
*/
module PolynomialReDoS {
private import TreeView
import codeql.regex.nfa.SuperlinearBackTracking::Make<TreeView>
/**
* A data flow source node for polynomial regular expression denial-of-service vulnerabilities.
*/
@@ -127,7 +129,7 @@ module PolynomialReDoS {
override DataFlow::Node getHighlight() { result = matchNode }
}
private predicate lengthGuard(CfgNodes::ExprCfgNode g, CfgNode node, boolean branch) {
private predicate lengthGuard(CfgNodes::AstCfgNode g, CfgNode node, boolean branch) {
exists(DataFlow::Node input, DataFlow::CallNode length, DataFlow::ExprNode operand |
length.asExpr().getExpr().(Ast::MethodCall).getMethodName() = "length" and
length.getReceiver() = input and

View File

@@ -3,155 +3,7 @@
* and for testing which capture groups are filled when a particular regexp matches a string.
*/
import NfaUtils
/** A root term */
class RootTerm extends RegExpTerm {
RootTerm() { this.isRootTerm() }
}
/**
* Holds if it should be tested whether `root` matches `str`.
*
* If `ignorePrefix` is true, then a regexp without a start anchor will be treated as if it had a start anchor.
* E.g. a regular expression `/foo$/` will match any string that ends with "foo",
* but if `ignorePrefix` is true, it will only match "foo".
*
* If `testWithGroups` is true, then the `RegexpMatching::fillsCaptureGroup` predicate can be used to determine which capture
* groups are filled by a string.
*/
signature predicate isRegexpMatchingCandidateSig(
RootTerm root, string str, boolean ignorePrefix, boolean testWithGroups
);
/**
* A module for determining if a regexp matches a given string,
* and reasoning about which capture groups are filled by a given string.
*
* The module parameter `isCandidate` determines which strings should be tested,
* and the results can be read from the `matches` and `fillsCaptureGroup` predicates.
*/
module RegexpMatching<isRegexpMatchingCandidateSig/4 isCandidate> {
/**
* Gets a state the regular expression `reg` can be in after matching the `i`th char in `str`.
* The regular expression is modeled as a non-determistic finite automaton,
* the regular expression can therefore be in multiple states after matching a character.
*
* It's a forward search to all possible states, and there is thus no guarantee that the state is on a path to an accepting state.
*/
private State getAState(RootTerm reg, int i, string str, boolean ignorePrefix) {
// start state, the -1 position before any chars have been matched
i = -1 and
isCandidate(reg, str, ignorePrefix, _) and
result.getRepr().getRootTerm() = reg and
isStartState(result)
or
// recursive case
result = getAStateAfterMatching(reg, _, str, i, _, ignorePrefix)
}
/**
* Gets the next state after the `prev` state from `reg`.
* `prev` is the state after matching `fromIndex` chars in `str`,
* and the result is the state after matching `toIndex` chars in `str`.
*
* This predicate is used as a step relation in the forwards search (`getAState`),
* and also as a step relation in the later backwards search (`getAStateThatReachesAccept`).
*/
private State getAStateAfterMatching(
RootTerm reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
) {
// the basic recursive case - outlined into a noopt helper to make performance work out.
result = getAStateAfterMatchingAux(reg, prev, str, toIndex, fromIndex, ignorePrefix)
or
// we can skip past word boundaries if the next char is a non-word char.
fromIndex = toIndex and
prev.getRepr() instanceof RegExpWordBoundary and
prev = getAState(reg, toIndex, str, ignorePrefix) and
after(prev.getRepr()) = result and
str.charAt(toIndex + 1).regexpMatch("\\W") // \W matches any non-word char.
}
pragma[noopt]
private State getAStateAfterMatchingAux(
RootTerm reg, State prev, string str, int toIndex, int fromIndex, boolean ignorePrefix
) {
prev = getAState(reg, fromIndex, str, ignorePrefix) and
fromIndex = toIndex - 1 and
exists(string char | char = str.charAt(toIndex) | specializedDeltaClosed(prev, char, result)) and
not discardedPrefixStep(prev, result, ignorePrefix)
}
/** Holds if a step from `prev` to `next` should be discarded when the `ignorePrefix` flag is set. */
private predicate discardedPrefixStep(State prev, State next, boolean ignorePrefix) {
prev = mkMatch(any(RegExpRoot r)) and
ignorePrefix = true and
next = prev
}
// The `deltaClosed` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
private predicate specializedDeltaClosed(State prev, string char, State next) {
deltaClosed(prev, specializedGetAnInputSymbolMatching(char), next)
}
// The `getAnInputSymbolMatching` relation specialized to the chars that exists in strings tested by a `MatchedRegExp`.
pragma[noinline]
private InputSymbol specializedGetAnInputSymbolMatching(string char) {
exists(string s, RootTerm r | isCandidate(r, s, _, _) | char = s.charAt(_)) and
result = getAnInputSymbolMatching(char)
}
/**
* Gets the `i`th state on a path to the accepting state when `reg` matches `str`.
* Starts with an accepting state as found by `getAState` and searches backwards
* to the start state through the reachable states (as found by `getAState`).
*
* This predicate satisfies the invariant that the result state can be reached with `i` steps from a start state,
* and an accepting state can be found after (`str.length() - 1 - i`) steps from the result.
* The result state is therefore always on a valid path where `reg` accepts `str`.
*
* This predicate is only used to find which capture groups a regular expression has filled,
* and thus the search is only performed for the strings in the `testWithGroups(..)` predicate.
*/
private State getAStateThatReachesAccept(RootTerm reg, int i, string str, boolean ignorePrefix) {
// base case, reaches an accepting state from the last state in `getAState(..)`
isCandidate(reg, str, ignorePrefix, true) and
i = str.length() - 1 and
result = getAState(reg, i, str, ignorePrefix) and
epsilonSucc*(result) = Accept(_)
or
// recursive case. `next` is the next state to be matched after matching `prev`.
// this predicate is doing a backwards search, so `prev` is the result we are looking for.
exists(State next, State prev, int fromIndex, int toIndex |
next = getAStateThatReachesAccept(reg, toIndex, str, ignorePrefix) and
next = getAStateAfterMatching(reg, prev, str, toIndex, fromIndex, ignorePrefix) and
i = fromIndex and
result = prev
)
}
/** Gets the capture group number that `term` belongs to. */
private int group(RegExpTerm term) {
exists(RegExpGroup grp | grp.getNumber() = result | term.getParent*() = grp)
}
/**
* Holds if `reg` matches `str`, where `str` is in the `isCandidate` predicate.
*/
predicate matches(RootTerm reg, string str) {
exists(State state | state = getAState(reg, str.length() - 1, str, _) |
epsilonSucc*(state) = Accept(_)
)
}
/**
* Holds if matching `str` against `reg` may fill capture group number `g`.
* Only holds if `str` is in the `testWithGroups` predicate.
*/
predicate fillsCaptureGroup(RootTerm reg, string str, int g) {
exists(State s |
s = getAStateThatReachesAccept(reg, _, str, _) and
g = group(s.getRepr())
)
}
}
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
// RegexpMatching should be used directly from the shared pack, and not from this file.
deprecated import codeql.regex.nfa.RegexpMatching::Make<TreeView> as Dep
import Dep

View File

@@ -1,11 +1,4 @@
/**
* Provides classes for working with regular expressions that can
* perform backtracking in superlinear time.
*/
import NfaUtils
/*
* This module implements the analysis described in the paper:
* Valentin Wustholz, Oswaldo Olivo, Marijn J. H. Heule, and Isil Dillig:
* Static Detection of DoS Vulnerabilities in
@@ -42,377 +35,7 @@ import NfaUtils
* It also doesn't find all transitions in the product automaton, which can cause false negatives.
*/
/**
* Gets any root (start) state of a regular expression.
*/
private State getRootState() { result = mkMatch(any(RegExpRoot r)) }
private newtype TStateTuple =
MkStateTuple(State q1, State q2, State q3) {
// starts at (pivot, pivot, succ)
isStartLoops(q1, q3) and q1 = q2
or
step(_, _, _, _, q1, q2, q3) and FeasibleTuple::isFeasibleTuple(q1, q2, q3)
}
/**
* A state in the product automaton.
* The product automaton contains 3-tuples of states.
*
* We lazily only construct those states that we are actually
* going to need.
* Either a start state `(pivot, pivot, succ)`, or a state
* where there exists a transition from an already existing state.
*
* The exponential variant of this query (`js/redos`) uses an optimization
* trick where `q1 <= q2`. This trick cannot be used here as the order
* of the elements matter.
*/
class StateTuple extends TStateTuple {
State q1;
State q2;
State q3;
StateTuple() { this = MkStateTuple(q1, q2, q3) }
/**
* Gest a string representation of this tuple.
*/
string toString() { result = "(" + q1 + ", " + q2 + ", " + q3 + ")" }
/**
* Holds if this tuple is `(r1, r2, r3)`.
*/
pragma[noinline]
predicate isTuple(State r1, State r2, State r3) { r1 = q1 and r2 = q2 and r3 = q3 }
}
/**
* A module for determining feasible tuples for the product automaton.
*
* The implementation is split into many predicates for performance reasons.
*/
private module FeasibleTuple {
/**
* Holds if the tuple `(r1, r2, r3)` might be on path from a start-state to an end-state in the product automaton.
*/
pragma[inline]
predicate isFeasibleTuple(State r1, State r2, State r3) {
// The first element is either inside a repetition (or the start state itself)
isRepetitionOrStart(r1) and
// The last element is inside a repetition
stateInsideRepetition(r3) and
// The states are reachable in the NFA in the order r1 -> r2 -> r3
delta+(r1) = r2 and
delta+(r2) = r3 and
// The first element can reach a beginning (the "pivot" state in a `(pivot, succ)` pair).
canReachABeginning(r1) and
// The last element can reach a target (the "succ" state in a `(pivot, succ)` pair).
canReachATarget(r3)
}
/**
* Holds if `s` is either inside a repetition, or is the start state (which is a repetition).
*/
pragma[noinline]
private predicate isRepetitionOrStart(State s) { stateInsideRepetition(s) or s = getRootState() }
/**
* Holds if state `s` might be inside a backtracking repetition.
*/
pragma[noinline]
private predicate stateInsideRepetition(State s) {
s.getRepr().getParent*() instanceof InfiniteRepetitionQuantifier
}
/**
* Holds if there exists a path in the NFA from `s` to a "pivot" state
* (from a `(pivot, succ)` pair that starts the search).
*/
pragma[noinline]
private predicate canReachABeginning(State s) {
delta+(s) = any(State pivot | isStartLoops(pivot, _))
}
/**
* Holds if there exists a path in the NFA from `s` to a "succ" state
* (from a `(pivot, succ)` pair that starts the search).
*/
pragma[noinline]
private predicate canReachATarget(State s) { delta+(s) = any(State succ | isStartLoops(_, succ)) }
}
/**
* Holds if `pivot` and `succ` are a pair of loops that could be the beginning of a quadratic blowup.
*
* There is a slight implementation difference compared to the paper: this predicate requires that `pivot != succ`.
* The case where `pivot = succ` causes exponential backtracking and is handled by the `js/redos` query.
*/
predicate isStartLoops(State pivot, State succ) {
pivot != succ and
succ.getRepr() instanceof InfiniteRepetitionQuantifier and
delta+(pivot) = succ and
(
pivot.getRepr() instanceof InfiniteRepetitionQuantifier
or
pivot = mkMatch(any(RegExpRoot root))
)
}
/**
* Gets a state for which there exists a transition in the NFA from `s'.
*/
State delta(State s) { delta(s, _, result) }
/**
* Holds if there are transitions from the components of `q` to the corresponding
* components of `r` labelled with `s1`, `s2`, and `s3`, respectively.
*/
pragma[noinline]
predicate step(StateTuple q, InputSymbol s1, InputSymbol s2, InputSymbol s3, StateTuple r) {
exists(State r1, State r2, State r3 |
step(q, s1, s2, s3, r1, r2, r3) and r = MkStateTuple(r1, r2, r3)
)
}
/**
* Holds if there are transitions from the components of `q` to `r1`, `r2`, and `r3
* labelled with `s1`, `s2`, and `s3`, respectively.
*/
pragma[noopt]
predicate step(
StateTuple q, InputSymbol s1, InputSymbol s2, InputSymbol s3, State r1, State r2, State r3
) {
exists(State q1, State q2, State q3 | q.isTuple(q1, q2, q3) |
deltaClosed(q1, s1, r1) and
deltaClosed(q2, s2, r2) and
deltaClosed(q3, s3, r3) and
// use noopt to force the join on `getAThreewayIntersect` to happen last.
exists(getAThreewayIntersect(s1, s2, s3))
)
}
/**
* Gets a char that is matched by all the edges `s1`, `s2`, and `s3`.
*
* The result is not complete, and might miss some combination of edges that share some character.
*/
pragma[noinline]
string getAThreewayIntersect(InputSymbol s1, InputSymbol s2, InputSymbol s3) {
result = minAndMaxIntersect(s1, s2) and result = [intersect(s2, s3), intersect(s1, s3)]
or
result = minAndMaxIntersect(s1, s3) and result = [intersect(s2, s3), intersect(s1, s2)]
or
result = minAndMaxIntersect(s2, s3) and result = [intersect(s1, s2), intersect(s1, s3)]
}
/**
* Gets the minimum and maximum characters that intersect between `a` and `b`.
* This predicate is used to limit the size of `getAThreewayIntersect`.
*/
pragma[noinline]
string minAndMaxIntersect(InputSymbol a, InputSymbol b) {
result = [min(intersect(a, b)), max(intersect(a, b))]
}
private newtype TTrace =
Nil() or
Step(InputSymbol s1, InputSymbol s2, InputSymbol s3, TTrace t) {
isReachableFromStartTuple(_, _, t, s1, s2, s3, _, _)
}
/**
* A list of tuples of input symbols that describe a path in the product automaton
* starting from some start state.
*/
class Trace extends TTrace {
/**
* Gets a string representation of this Trace that can be used for debug purposes.
*/
string toString() {
this = Nil() and result = "Nil()"
or
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace t | this = Step(s1, s2, s3, t) |
result = "Step(" + s1 + ", " + s2 + ", " + s3 + ", " + t + ")"
)
}
}
/**
* Holds if there exists a transition from `r` to `q` in the product automaton.
* Notice that the arguments are flipped, and thus the direction is backwards.
*/
pragma[noinline]
predicate tupleDeltaBackwards(StateTuple q, StateTuple r) { step(r, _, _, _, q) }
/**
* Holds if `tuple` is an end state in our search.
* That means there exists a pair of loops `(pivot, succ)` such that `tuple = (pivot, succ, succ)`.
*/
predicate isEndTuple(StateTuple tuple) { tuple = getAnEndTuple(_, _) }
/**
* Gets the minimum length of a path from `r` to some an end state `end`.
*
* The implementation searches backwards from the end-tuple.
* This approach was chosen because it is way more efficient if the first predicate given to `shortestDistances` is small.
* The `end` argument must always be an end state.
*/
int distBackFromEnd(StateTuple r, StateTuple end) =
shortestDistances(isEndTuple/1, tupleDeltaBackwards/2)(end, r, result)
/**
* Holds if there exists a pair of repetitions `(pivot, succ)` in the regular expression such that:
* `tuple` is reachable from `(pivot, pivot, succ)` in the product automaton,
* and there is a distance of `dist` from `tuple` to the nearest end-tuple `(pivot, succ, succ)`,
* and a path from a start-state to `tuple` follows the transitions in `trace`.
*/
private predicate isReachableFromStartTuple(
State pivot, State succ, StateTuple tuple, Trace trace, int dist
) {
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace v |
isReachableFromStartTuple(pivot, succ, v, s1, s2, s3, tuple, dist) and
trace = Step(s1, s2, s3, v)
)
}
private predicate isReachableFromStartTuple(
State pivot, State succ, Trace trace, InputSymbol s1, InputSymbol s2, InputSymbol s3,
StateTuple tuple, int dist
) {
// base case.
exists(State q1, State q2, State q3 |
isStartLoops(pivot, succ) and
step(MkStateTuple(pivot, pivot, succ), s1, s2, s3, tuple) and
tuple = MkStateTuple(q1, q2, q3) and
trace = Nil() and
dist = distBackFromEnd(tuple, MkStateTuple(pivot, succ, succ))
)
or
// recursive case
exists(StateTuple p |
isReachableFromStartTuple(pivot, succ, p, trace, dist + 1) and
dist = distBackFromEnd(tuple, MkStateTuple(pivot, succ, succ)) and
step(p, s1, s2, s3, tuple)
)
}
/**
* Gets the tuple `(pivot, succ, succ)` from the product automaton.
*/
StateTuple getAnEndTuple(State pivot, State succ) {
isStartLoops(pivot, succ) and
result = MkStateTuple(pivot, succ, succ)
}
/** An implementation of a chain containing chars for use by `Concretizer`. */
private module CharTreeImpl implements CharTree {
class CharNode = Trace;
CharNode getPrev(CharNode t) { t = Step(_, _, _, result) }
/** Holds if `n` is used in `isPumpable`. */
predicate isARelevantEnd(CharNode n) {
exists(State pivot, State succ |
isReachableFromStartTuple(pivot, succ, getAnEndTuple(pivot, succ), n, _)
)
}
string getChar(CharNode t) {
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3 | t = Step(s1, s2, s3, _) |
result = getAThreewayIntersect(s1, s2, s3)
)
}
}
/**
* Holds if matching repetitions of `pump` can:
* 1) Transition from `pivot` back to `pivot`.
* 2) Transition from `pivot` to `succ`.
* 3) Transition from `succ` to `succ`.
*
* From theorem 3 in the paper linked in the top of this file we can therefore conclude that
* the regular expression has polynomial backtracking - if a rejecting suffix exists.
*
* This predicate is used by `SuperLinearReDoSConfiguration`, and the final results are
* available in the `hasReDoSResult` predicate.
*/
predicate isPumpable(State pivot, State succ, string pump) {
exists(StateTuple q, Trace t |
isReachableFromStartTuple(pivot, succ, q, t, _) and
q = getAnEndTuple(pivot, succ) and
pump = Concretizer<CharTreeImpl>::concretize(t)
)
}
/**
* Holds if states starting in `state` can have polynomial backtracking with the string `pump`.
*/
predicate isReDoSCandidate(State state, string pump) { isPumpable(_, state, pump) }
/**
* Holds if repetitions of `pump` at `t` will cause polynomial backtracking.
*/
predicate polynomialReDoS(RegExpTerm t, string pump, string prefixMsg, RegExpTerm prev) {
exists(State s, State pivot |
ReDoSPruning<isReDoSCandidate/2>::hasReDoSResult(t, pump, s, prefixMsg) and
isPumpable(pivot, s, _) and
prev = pivot.getRepr()
)
}
/**
* Gets a message for why `term` can cause polynomial backtracking.
*/
string getReasonString(RegExpTerm term, string pump, string prefixMsg, RegExpTerm prev) {
polynomialReDoS(term, pump, prefixMsg, prev) and
result =
"Strings " + prefixMsg + "with many repetitions of '" + pump +
"' can start matching anywhere after the start of the preceeding " + prev
}
/**
* A term that may cause a regular expression engine to perform a
* polynomial number of match attempts, relative to the input length.
*/
class PolynomialBackTrackingTerm extends InfiniteRepetitionQuantifier {
string reason;
string pump;
string prefixMsg;
RegExpTerm prev;
PolynomialBackTrackingTerm() {
reason = getReasonString(this, pump, prefixMsg, prev) and
// there might be many reasons for this term to have polynomial backtracking - we pick the shortest one.
reason = min(string msg | msg = getReasonString(this, _, _, _) | msg order by msg.length(), msg)
}
/**
* Holds if all non-empty successors to the polynomial backtracking term matches the end of the line.
*/
predicate isAtEndLine() {
forall(RegExpTerm succ | this.getSuccessor+() = succ and not matchesEpsilon(succ) |
succ instanceof RegExpDollar
)
}
/**
* Gets the string that should be repeated to cause this regular expression to perform polynomially.
*/
string getPumpString() { result = pump }
/**
* Gets a message for which prefix a matching string must start with for this term to cause polynomial backtracking.
*/
string getPrefixMessage() { result = prefixMsg }
/**
* Gets a predecessor to `this`, which also loops on the pump string, and thereby causes polynomial backtracking.
*/
RegExpTerm getPreviousLoop() { result = prev }
/**
* Gets the reason for the number of match attempts.
*/
string getReason() { result = reason }
}
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
// SuperlinearBackTracking should be used directly from the shared pack, and not from this file.
deprecated private import codeql.regex.nfa.SuperlinearBackTracking::Make<TreeView> as Dep
import Dep

View File

@@ -1,5 +1,5 @@
name: codeql/ruby-all
version: 0.4.4-dev
version: 0.4.5-dev
groups: ruby
extractor: ruby
dbscheme: ruby.dbscheme
@@ -7,4 +7,4 @@ upgrades: upgrades
library: true
dependencies:
codeql/ssa: ${workspace}
codeql/regex: ${workspace}

View File

@@ -1,3 +1,13 @@
## 0.4.4
### New Queries
* Added a new query, `rb/shell-command-constructed-from-input`, to detect libraries that unsafely construct shell commands from their inputs.
### Minor Analysis Improvements
* The `rb/sql-injection` query now considers consider SQL constructions, such as calls to `Arel.sql`, as sinks.
## 0.4.3
### Minor Analysis Improvements

View File

@@ -1,4 +0,0 @@
---
category: newQuery
---
* Added a new query, `rb/shell-command-constructed-from-input`, to detect libraries that unsafely construct shell commands from their inputs.

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* Added a new query, `rb/stack-trace-exposure`, to detect exposure of stack-traces to users via HTTP responses.

View File

@@ -0,0 +1,9 @@
## 0.4.4
### New Queries
* Added a new query, `rb/shell-command-constructed-from-input`, to detect libraries that unsafely construct shell commands from their inputs.
### Minor Analysis Improvements
* The `rb/sql-injection` query now considers consider SQL constructions, such as calls to `Arel.sql`, as sinks.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.4.3
lastReleaseVersion: 0.4.4

View File

@@ -1,5 +1,5 @@
name: codeql/ruby-queries
version: 0.4.4-dev
version: 0.4.5-dev
groups:
- ruby
- queries

View File

@@ -12,17 +12,18 @@
* external/cwe/cwe-020
*/
import codeql.ruby.security.OverlyLargeRangeQuery
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
import codeql.regex.OverlyLargeRangeQuery::Make<TreeView>
RegExpCharacterClass potentialMisparsedCharClass() {
TreeView::RegExpCharacterClass potentialMisparsedCharClass() {
// some escapes, e.g. [\000-\037] are currently misparsed.
result.getAChild().(RegExpNormalChar).getValue() = "\\"
result.getAChild().(TreeView::RegExpNormalChar).getValue() = "\\"
or
// nested char classes are currently misparsed
result.getAChild().(RegExpNormalChar).getValue() = "["
result.getAChild().(TreeView::RegExpNormalChar).getValue() = "["
}
from RegExpCharacterRange range, string reason
from TreeView::RegExpCharacterRange range, string reason
where
problem(range, reason) and
not range.getParent() = potentialMisparsedCharClass()

View File

@@ -11,28 +11,11 @@
* external/cwe/cwe-089
*/
import codeql.ruby.AST
import codeql.ruby.Concepts
import codeql.ruby.DataFlow
import codeql.ruby.dataflow.BarrierGuards
import codeql.ruby.dataflow.RemoteFlowSources
import codeql.ruby.TaintTracking
import codeql.ruby.security.SqlInjectionQuery
import DataFlow::PathGraph
class SqlInjectionConfiguration extends TaintTracking::Configuration {
SqlInjectionConfiguration() { this = "SQLInjectionConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof SqlExecution }
override predicate isSanitizer(DataFlow::Node node) {
node instanceof StringConstCompareBarrier or
node instanceof StringConstArrayInclusionCallBarrier
}
}
from SqlInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This SQL query depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -14,7 +14,8 @@
* external/cwe/cwe-186
*/
import codeql.ruby.security.BadTagFilterQuery
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
import codeql.regex.nfa.BadTagFilterQuery::Make<TreeView>
from HtmlMatchingRegExp regexp, string msg
where msg = min(string m | isBadRegexpFilter(regexp, m) | m order by m.length(), m) // there might be multiple, we arbitrarily pick the shortest one

View File

@@ -16,11 +16,10 @@
import DataFlow::PathGraph
import codeql.ruby.DataFlow
import codeql.ruby.security.regexp.PolynomialReDoSQuery
import codeql.ruby.security.regexp.SuperlinearBackTracking
from
PolynomialReDoS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
PolynomialReDoS::Sink sinkNode, PolynomialBackTrackingTerm regexp
PolynomialReDoS::Sink sinkNode, PolynomialReDoS::PolynomialBackTrackingTerm regexp
where
config.hasFlowPath(source, sink) and
sinkNode = sink.getNode() and

View File

@@ -14,11 +14,10 @@
* external/cwe/cwe-400
*/
import codeql.ruby.security.regexp.ExponentialBackTracking
import codeql.ruby.security.regexp.NfaUtils
import codeql.ruby.Regexp
private import codeql.ruby.regexp.RegExpTreeView::RegexTreeView as TreeView
import codeql.regex.nfa.ExponentialBackTracking::Make<TreeView>
from RegExpTerm t, string pump, State s, string prefixMsg
from TreeView::RegExpTerm t, string pump, State s, string prefixMsg
where hasReDoSResult(t, pump, s, prefixMsg)
select t,
"This part of the regular expression may cause exponential backtracking on strings " + prefixMsg +

View File

@@ -0,0 +1,49 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Software developers often add stack traces to error messages, as a debugging
aid. Whenever that error message occurs for an end user, the developer can use
the stack trace to help identify how to fix the problem. In particular, stack
traces can tell the developer more about the sequence of events that led to a
failure, as opposed to merely the final state of the software when the error
occurred.
</p>
<p>
Unfortunately, the same information can be useful to an attacker. The sequence
of class or method names in a stack trace can reveal the structure of the
application as well as any internal components it relies on. Furthermore, the
error message at the top of a stack trace can include information such as
server-side file names and SQL code that the application relies on, allowing an
attacker to fine-tune a subsequent injection attack.
</p>
</overview>
<recommendation>
<p>
Send the user a more generic error message that reveals less information.
Either suppress the stack trace entirely, or log it only on the server.
</p>
</recommendation>
<example>
<p>
In the following example, an exception is handled in two different ways. In the
first version, labeled BAD, the exception is exposed to the remote user by
rendering it as an HTTP response. As such, the user is able to see a detailed
stack trace, which may contain sensitive information. In the second version, the
error message is logged only on the server, and a generic error message is
displayed to the user. That way, the developers can still access and use the
error log, but remote users will not see the information. </p>
<sample src="examples/StackTraceExposure.rb" />
</example>
<references>
<li>OWASP: <a href="https://owasp.org/www-community/Improper_Error_Handling">Improper Error Handling</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Information exposure through an exception
* @description Leaking information about an exception, such as messages and stack traces, to an
* external user can expose implementation details that are useful to an attacker for
* developing a subsequent exploit.
* @kind path-problem
* @problem.severity error
* @security-severity 5.4
* @precision high
* @id rb/stack-trace-exposure
* @tags security
* external/cwe/cwe-209
* external/cwe/cwe-497
*/
import codeql.ruby.DataFlow
import codeql.ruby.security.StackTraceExposureQuery
import DataFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ can be exposed to an external user.", source.getNode(),
"Error information"

View File

@@ -0,0 +1,18 @@
class UsersController < ApplicationController
def update_bad(id)
do_computation()
rescue => e
# BAD
render body: e.backtrace, content_type: "text/plain"
end
def update_good(id)
do_computation()
rescue => e
# GOOD
logger.error e.backtrace
render body: "Computation failed", content_type: "text/plain"
end
end

View File

@@ -3,7 +3,6 @@
*
* Example for a test.ql:
* ```ql
* *import codeql.ruby.AST
* import TestUtilities.InlineFlowTest
* import PathGraph
*

View File

@@ -526,17 +526,20 @@ case.rb:
#-----| -> exit if_in_case (normal)
# 2| call to x1
#-----| -> when ...
#-----| -> 1
# 2| self
#-----| -> call to x1
# 3| when ...
#-----| -> 1
# 3| [match] when ...
#-----| match -> self
# 3| [no-match] when ...
#-----| no-match -> 2
# 3| 1
#-----| match -> self
#-----| no-match -> when ...
#-----| match -> [match] when ...
#-----| no-match -> [no-match] when ...
# 3| then ...
#-----| -> case ...
@@ -569,12 +572,15 @@ case.rb:
# 3| x2
#-----| -> "x2"
# 4| when ...
#-----| -> 2
# 4| [match] when ...
#-----| match -> self
# 4| [no-match] when ...
#-----| no-match -> case ...
# 4| 2
#-----| no-match -> case ...
#-----| match -> self
#-----| match -> [match] when ...
#-----| no-match -> [no-match] when ...
# 4| then ...
#-----| -> case ...
@@ -811,7 +817,7 @@ case.rb:
#-----| -> [ ..., * ]
# 35| [ ..., * ]
#-----| match -> x
#-----| false, match, true -> x
#-----| no-match -> in ... then ...
# 35| x
@@ -821,7 +827,7 @@ case.rb:
#-----| -> [ ..., * ]
# 36| [ ..., * ]
#-----| match -> x
#-----| false, match, true -> x
#-----| no-match -> in ... then ...
# 36| x
@@ -836,7 +842,7 @@ case.rb:
# 37| [ ..., * ]
#-----| raise -> exit case_match_array (abnormal)
#-----| match -> a
#-----| false, match, true -> a
# 37| a
#-----| match -> b
@@ -881,7 +887,7 @@ case.rb:
# 43| [ *,...,* ]
#-----| raise -> exit case_match_find (abnormal)
#-----| match -> x
#-----| false, match, true -> x
# 43| x
#-----| -> 1
@@ -931,7 +937,7 @@ case.rb:
#-----| no-match -> in ... then ...
# 49| { ..., ** }
#-----| match -> 1
#-----| false, match, true -> 1
#-----| no-match -> in ... then ...
# 49| 1
@@ -952,7 +958,7 @@ case.rb:
#-----| no-match -> in ... then ...
# 50| { ..., ** }
#-----| match -> 1
#-----| false, match, true -> 1
#-----| no-match -> in ... then ...
# 50| 1
@@ -968,7 +974,7 @@ case.rb:
# 51| { ..., ** }
#-----| raise -> exit case_match_hash (abnormal)
#-----| match -> case ...
#-----| false, match, true -> case ...
# 55| case_match_variable
#-----| -> case_match_underscore
@@ -1372,7 +1378,7 @@ case.rb:
# 91| { ..., ** }
#-----| raise -> exit case_match_various (abnormal)
#-----| match -> case ...
#-----| false, match, true -> case ...
# 95| case_match_guard_no_else
#-----| -> exit case.rb (normal)
@@ -1826,17 +1832,20 @@ cfg.rb:
#-----| -> call to puts
# 41| case ...
#-----| -> when ...
#-----| -> b
# 41| 10
#-----| -> when ...
# 42| when ...
#-----| -> 1
# 42| 1
# 42| [match] when ...
#-----| match -> self
#-----| no-match -> when ...
# 42| [no-match] when ...
#-----| no-match -> 2
# 42| 1
#-----| match -> [match] when ...
#-----| no-match -> [no-match] when ...
# 42| then ...
#-----| -> case ...
@@ -1853,20 +1862,23 @@ cfg.rb:
# 42| one
#-----| -> "one"
# 43| when ...
#-----| -> 2
# 43| [match] when ...
#-----| match -> self
# 43| [no-match] when ...
#-----| no-match -> self
# 43| 2
#-----| match -> [match] when ...
#-----| no-match -> 3
#-----| match -> self
# 43| 3
#-----| match -> [match] when ...
#-----| no-match -> 4
#-----| match -> self
# 43| 4
#-----| match -> self
#-----| no-match -> self
#-----| match -> [match] when ...
#-----| no-match -> [no-match] when ...
# 43| then ...
#-----| -> case ...
@@ -1901,15 +1913,18 @@ cfg.rb:
# 47| case ...
#-----| -> chained
# 48| when ...
#-----| -> b
# 48| [false] when ...
#-----| false -> b
# 48| [true] when ...
#-----| true -> self
# 48| b
#-----| -> 1
# 48| ... == ...
#-----| true -> self
#-----| false -> when ...
#-----| false -> [false] when ...
#-----| true -> [true] when ...
# 48| 1
#-----| -> ... == ...
@@ -1929,15 +1944,18 @@ cfg.rb:
# 48| one
#-----| -> "one"
# 49| when ...
#-----| -> b
# 49| [false] when ...
#-----| false -> case ...
# 49| [true] when ...
#-----| true -> self
# 49| b
#-----| -> 0
# 49| ... == ...
#-----| true -> [true] when ...
#-----| false -> b
#-----| true -> self
# 49| 0
#-----| -> ... == ...
@@ -1946,8 +1964,8 @@ cfg.rb:
#-----| -> 1
# 49| ... > ...
#-----| false -> case ...
#-----| true -> self
#-----| false -> [false] when ...
#-----| true -> [true] when ...
# 49| 1
#-----| -> ... > ...
@@ -2559,7 +2577,7 @@ cfg.rb:
#-----| -> type
# 101| value
#-----| no-match -> 42
#-----| false, no-match, true -> 42
#-----| match -> key
# 101| 42
@@ -6535,7 +6553,7 @@ raise.rb:
#-----| -> m11
# 121| p
#-----| no-match -> self
#-----| false, no-match, true -> self
#-----| match -> self
# 121| call to raise

View File

@@ -1,4 +1,5 @@
WARNING: Type BarrierGuard has been deprecated and may be removed in future (barrier-guards.ql:9,3-15)
WARNING: Type BarrierGuard has been deprecated and may be removed in future (barrier-guards.ql:10,3-15)
failures
oldStyleBarrierGuards
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:5:4:7 | foo | barrier-guards.rb:3:4:3:6 | foo | true |
| barrier-guards.rb:9:4:9:24 | call to include? | barrier-guards.rb:10:5:10:7 | foo | barrier-guards.rb:9:21:9:23 | foo | true |
@@ -9,6 +10,15 @@ oldStyleBarrierGuards
| barrier-guards.rb:43:4:43:15 | ... == ... | barrier-guards.rb:45:9:45:11 | foo | barrier-guards.rb:43:4:43:6 | foo | true |
| barrier-guards.rb:70:4:70:21 | call to include? | barrier-guards.rb:71:5:71:7 | foo | barrier-guards.rb:70:18:70:20 | foo | true |
| barrier-guards.rb:82:4:82:25 | ... != ... | barrier-guards.rb:83:5:83:7 | foo | barrier-guards.rb:82:15:82:17 | foo | true |
| barrier-guards.rb:207:4:207:15 | ... == ... | barrier-guards.rb:208:5:208:7 | foo | barrier-guards.rb:207:4:207:6 | foo | true |
| barrier-guards.rb:211:10:211:21 | ... == ... | barrier-guards.rb:212:5:212:7 | foo | barrier-guards.rb:211:10:211:12 | foo | true |
| barrier-guards.rb:215:16:215:27 | ... == ... | barrier-guards.rb:216:5:216:7 | foo | barrier-guards.rb:215:16:215:18 | foo | true |
| barrier-guards.rb:219:4:219:15 | ... == ... | barrier-guards.rb:219:21:219:23 | foo | barrier-guards.rb:219:4:219:6 | foo | true |
| barrier-guards.rb:219:4:219:15 | ... == ... | barrier-guards.rb:220:5:220:7 | foo | barrier-guards.rb:219:4:219:6 | foo | true |
| barrier-guards.rb:219:21:219:32 | ... == ... | barrier-guards.rb:220:5:220:7 | foo | barrier-guards.rb:219:21:219:23 | foo | true |
| barrier-guards.rb:232:6:232:17 | ... == ... | barrier-guards.rb:233:5:233:7 | foo | barrier-guards.rb:232:6:232:8 | foo | true |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:237:24:237:26 | foo | barrier-guards.rb:237:6:237:8 | foo | true |
| barrier-guards.rb:268:1:268:12 | ... == ... | barrier-guards.rb:268:17:268:19 | foo | barrier-guards.rb:268:1:268:3 | foo | true |
newStyleBarrierGuards
| barrier-guards.rb:4:5:4:7 | foo |
| barrier-guards.rb:10:5:10:7 | foo |
@@ -20,6 +30,26 @@ newStyleBarrierGuards
| barrier-guards.rb:71:5:71:7 | foo |
| barrier-guards.rb:83:5:83:7 | foo |
| barrier-guards.rb:91:5:91:7 | foo |
| barrier-guards.rb:126:5:126:7 | foo |
| barrier-guards.rb:133:5:133:7 | foo |
| barrier-guards.rb:135:5:135:7 | foo |
| barrier-guards.rb:140:5:140:7 | foo |
| barrier-guards.rb:142:5:142:7 | foo |
| barrier-guards.rb:149:5:149:7 | foo |
| barrier-guards.rb:154:5:154:7 | foo |
| barrier-guards.rb:159:5:159:7 | foo |
| barrier-guards.rb:164:5:164:7 | foo |
| barrier-guards.rb:192:5:192:7 | foo |
| barrier-guards.rb:196:5:196:7 | foo |
| barrier-guards.rb:208:5:208:7 | foo |
| barrier-guards.rb:212:5:212:7 | foo |
| barrier-guards.rb:216:5:216:7 | foo |
| barrier-guards.rb:219:21:219:23 | foo |
| barrier-guards.rb:220:5:220:7 | foo |
| barrier-guards.rb:233:5:233:7 | foo |
| barrier-guards.rb:237:24:237:26 | foo |
| barrier-guards.rb:244:5:244:7 | foo |
| barrier-guards.rb:268:17:268:19 | foo |
controls
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:5:4:7 | foo | true |
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:6:5:6:7 | foo | false |
@@ -67,3 +97,228 @@ controls
| barrier-guards.rb:118:8:118:8 | call to x | barrier-guards.rb:118:4:118:8 | [true] not ... | false |
| barrier-guards.rb:118:8:118:8 | call to x | barrier-guards.rb:119:5:119:7 | foo | false |
| barrier-guards.rb:118:8:118:8 | call to x | barrier-guards.rb:121:5:121:8 | bars | true |
| barrier-guards.rb:125:1:126:19 | [match] when ... | barrier-guards.rb:126:5:126:7 | foo | match |
| barrier-guards.rb:125:1:126:19 | [no-match] when ... | barrier-guards.rb:128:5:128:7 | foo | no-match |
| barrier-guards.rb:125:6:125:10 | "foo" | barrier-guards.rb:125:1:126:19 | [match] when ... | match |
| barrier-guards.rb:125:6:125:10 | "foo" | barrier-guards.rb:125:1:126:19 | [no-match] when ... | no-match |
| barrier-guards.rb:125:6:125:10 | "foo" | barrier-guards.rb:126:5:126:7 | foo | match |
| barrier-guards.rb:125:6:125:10 | "foo" | barrier-guards.rb:128:5:128:7 | foo | no-match |
| barrier-guards.rb:132:1:133:19 | [match] when ... | barrier-guards.rb:133:5:133:7 | foo | match |
| barrier-guards.rb:132:1:133:19 | [no-match] when ... | barrier-guards.rb:134:1:135:19 | [match] when ... | no-match |
| barrier-guards.rb:132:1:133:19 | [no-match] when ... | barrier-guards.rb:134:1:135:19 | [no-match] when ... | no-match |
| barrier-guards.rb:132:1:133:19 | [no-match] when ... | barrier-guards.rb:134:7:134:9 | bar | no-match |
| barrier-guards.rb:132:1:133:19 | [no-match] when ... | barrier-guards.rb:135:5:135:7 | foo | no-match |
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:132:1:133:19 | [match] when ... | match |
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:132:1:133:19 | [no-match] when ... | no-match |
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:133:5:133:7 | foo | match |
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:134:1:135:19 | [match] when ... | no-match |
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:134:1:135:19 | [no-match] when ... | no-match |
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:134:7:134:9 | bar | no-match |
| barrier-guards.rb:132:6:132:10 | "foo" | barrier-guards.rb:135:5:135:7 | foo | no-match |
| barrier-guards.rb:134:1:135:19 | [match] when ... | barrier-guards.rb:135:5:135:7 | foo | match |
| barrier-guards.rb:134:6:134:10 | "bar" | barrier-guards.rb:134:1:135:19 | [match] when ... | match |
| barrier-guards.rb:134:6:134:10 | "bar" | barrier-guards.rb:134:1:135:19 | [no-match] when ... | no-match |
| barrier-guards.rb:134:6:134:10 | "bar" | barrier-guards.rb:135:5:135:7 | foo | match |
| barrier-guards.rb:139:1:140:19 | [match] when ... | barrier-guards.rb:140:5:140:7 | foo | match |
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:141:1:142:19 | [match] when ... | no-match |
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:141:1:142:19 | [no-match] when ... | no-match |
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:141:7:141:9 | baz | no-match |
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:141:14:141:17 | quux | no-match |
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:142:5:142:7 | foo | no-match |
| barrier-guards.rb:139:1:140:19 | [no-match] when ... | barrier-guards.rb:144:5:144:7 | foo | no-match |
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:139:1:140:19 | [no-match] when ... | no-match |
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:139:14:139:16 | bar | no-match |
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:141:1:142:19 | [match] when ... | no-match |
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:141:1:142:19 | [no-match] when ... | no-match |
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:141:7:141:9 | baz | no-match |
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:141:14:141:17 | quux | no-match |
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:142:5:142:7 | foo | no-match |
| barrier-guards.rb:139:6:139:10 | "foo" | barrier-guards.rb:144:5:144:7 | foo | no-match |
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:139:1:140:19 | [no-match] when ... | no-match |
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:141:1:142:19 | [match] when ... | no-match |
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:141:1:142:19 | [no-match] when ... | no-match |
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:141:7:141:9 | baz | no-match |
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:141:14:141:17 | quux | no-match |
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:142:5:142:7 | foo | no-match |
| barrier-guards.rb:139:13:139:17 | "bar" | barrier-guards.rb:144:5:144:7 | foo | no-match |
| barrier-guards.rb:141:1:142:19 | [match] when ... | barrier-guards.rb:142:5:142:7 | foo | match |
| barrier-guards.rb:141:1:142:19 | [no-match] when ... | barrier-guards.rb:144:5:144:7 | foo | no-match |
| barrier-guards.rb:141:6:141:10 | "baz" | barrier-guards.rb:141:1:142:19 | [no-match] when ... | no-match |
| barrier-guards.rb:141:6:141:10 | "baz" | barrier-guards.rb:141:14:141:17 | quux | no-match |
| barrier-guards.rb:141:6:141:10 | "baz" | barrier-guards.rb:144:5:144:7 | foo | no-match |
| barrier-guards.rb:141:13:141:18 | "quux" | barrier-guards.rb:141:1:142:19 | [no-match] when ... | no-match |
| barrier-guards.rb:141:13:141:18 | "quux" | barrier-guards.rb:144:5:144:7 | foo | no-match |
| barrier-guards.rb:148:1:149:19 | [match] when ... | barrier-guards.rb:149:5:149:7 | foo | match |
| barrier-guards.rb:148:6:148:20 | * ... | barrier-guards.rb:148:1:149:19 | [match] when ... | match |
| barrier-guards.rb:148:6:148:20 | * ... | barrier-guards.rb:148:1:149:19 | [no-match] when ... | no-match |
| barrier-guards.rb:148:6:148:20 | * ... | barrier-guards.rb:149:5:149:7 | foo | match |
| barrier-guards.rb:153:1:154:19 | [match] when ... | barrier-guards.rb:154:5:154:7 | foo | match |
| barrier-guards.rb:153:6:153:17 | * ... | barrier-guards.rb:153:1:154:19 | [match] when ... | match |
| barrier-guards.rb:153:6:153:17 | * ... | barrier-guards.rb:153:1:154:19 | [no-match] when ... | no-match |
| barrier-guards.rb:153:6:153:17 | * ... | barrier-guards.rb:154:5:154:7 | foo | match |
| barrier-guards.rb:158:1:159:19 | [match] when ... | barrier-guards.rb:159:5:159:7 | foo | match |
| barrier-guards.rb:158:6:158:9 | * ... | barrier-guards.rb:158:1:159:19 | [match] when ... | match |
| barrier-guards.rb:158:6:158:9 | * ... | barrier-guards.rb:158:1:159:19 | [no-match] when ... | no-match |
| barrier-guards.rb:158:6:158:9 | * ... | barrier-guards.rb:159:5:159:7 | foo | match |
| barrier-guards.rb:163:1:164:19 | [match] when ... | barrier-guards.rb:164:5:164:7 | foo | match |
| barrier-guards.rb:163:6:163:10 | * ... | barrier-guards.rb:163:1:164:19 | [match] when ... | match |
| barrier-guards.rb:163:6:163:10 | * ... | barrier-guards.rb:163:1:164:19 | [no-match] when ... | no-match |
| barrier-guards.rb:163:6:163:10 | * ... | barrier-guards.rb:164:5:164:7 | foo | match |
| barrier-guards.rb:168:1:169:7 | [match] when ... | barrier-guards.rb:169:5:169:7 | foo | match |
| barrier-guards.rb:168:6:168:16 | * ... | barrier-guards.rb:168:1:169:7 | [match] when ... | match |
| barrier-guards.rb:168:6:168:16 | * ... | barrier-guards.rb:168:1:169:7 | [no-match] when ... | no-match |
| barrier-guards.rb:168:6:168:16 | * ... | barrier-guards.rb:169:5:169:7 | foo | match |
| barrier-guards.rb:173:1:174:7 | [match] when ... | barrier-guards.rb:174:5:174:7 | foo | match |
| barrier-guards.rb:173:6:173:10 | "foo" | barrier-guards.rb:173:1:174:7 | [no-match] when ... | no-match |
| barrier-guards.rb:173:6:173:10 | "foo" | barrier-guards.rb:173:13:173:13 | self | no-match |
| barrier-guards.rb:173:13:173:13 | call to x | barrier-guards.rb:173:1:174:7 | [no-match] when ... | no-match |
| barrier-guards.rb:180:1:181:7 | [match] when ... | barrier-guards.rb:181:5:181:7 | foo | match |
| barrier-guards.rb:180:6:180:15 | * ... | barrier-guards.rb:180:1:181:7 | [match] when ... | match |
| barrier-guards.rb:180:6:180:15 | * ... | barrier-guards.rb:180:1:181:7 | [no-match] when ... | no-match |
| barrier-guards.rb:180:6:180:15 | * ... | barrier-guards.rb:181:5:181:7 | foo | match |
| barrier-guards.rb:187:1:188:7 | [match] when ... | barrier-guards.rb:188:5:188:7 | foo | match |
| barrier-guards.rb:187:6:187:15 | * ... | barrier-guards.rb:187:1:188:7 | [match] when ... | match |
| barrier-guards.rb:187:6:187:15 | * ... | barrier-guards.rb:187:1:188:7 | [no-match] when ... | no-match |
| barrier-guards.rb:187:6:187:15 | * ... | barrier-guards.rb:188:5:188:7 | foo | match |
| barrier-guards.rb:191:4:191:15 | ... == ... | barrier-guards.rb:191:4:191:31 | [false] ... or ... | false |
| barrier-guards.rb:191:4:191:15 | ... == ... | barrier-guards.rb:191:20:191:22 | foo | false |
| barrier-guards.rb:191:4:191:31 | [true] ... or ... | barrier-guards.rb:192:5:192:7 | foo | true |
| barrier-guards.rb:191:20:191:31 | ... == ... | barrier-guards.rb:191:4:191:31 | [false] ... or ... | false |
| barrier-guards.rb:195:4:195:15 | ... == ... | barrier-guards.rb:195:4:195:31 | [false] ... or ... | false |
| barrier-guards.rb:195:4:195:15 | ... == ... | barrier-guards.rb:195:4:195:47 | [false] ... or ... | false |
| barrier-guards.rb:195:4:195:15 | ... == ... | barrier-guards.rb:195:20:195:22 | foo | false |
| barrier-guards.rb:195:4:195:15 | ... == ... | barrier-guards.rb:195:36:195:38 | foo | false |
| barrier-guards.rb:195:4:195:31 | [false] ... or ... | barrier-guards.rb:195:4:195:47 | [false] ... or ... | false |
| barrier-guards.rb:195:4:195:31 | [false] ... or ... | barrier-guards.rb:195:36:195:38 | foo | false |
| barrier-guards.rb:195:4:195:47 | [true] ... or ... | barrier-guards.rb:196:5:196:7 | foo | true |
| barrier-guards.rb:195:20:195:31 | ... == ... | barrier-guards.rb:195:4:195:31 | [false] ... or ... | false |
| barrier-guards.rb:195:20:195:31 | ... == ... | barrier-guards.rb:195:4:195:47 | [false] ... or ... | false |
| barrier-guards.rb:195:20:195:31 | ... == ... | barrier-guards.rb:195:36:195:38 | foo | false |
| barrier-guards.rb:195:36:195:47 | ... == ... | barrier-guards.rb:195:4:195:47 | [false] ... or ... | false |
| barrier-guards.rb:199:4:199:15 | ... == ... | barrier-guards.rb:199:4:199:31 | [false] ... or ... | false |
| barrier-guards.rb:199:4:199:15 | ... == ... | barrier-guards.rb:199:4:199:43 | [false] ... or ... | false |
| barrier-guards.rb:199:4:199:15 | ... == ... | barrier-guards.rb:199:20:199:22 | foo | false |
| barrier-guards.rb:199:4:199:15 | ... == ... | barrier-guards.rb:199:36:199:38 | foo | false |
| barrier-guards.rb:199:4:199:31 | [false] ... or ... | barrier-guards.rb:199:4:199:43 | [false] ... or ... | false |
| barrier-guards.rb:199:4:199:31 | [false] ... or ... | barrier-guards.rb:199:36:199:38 | foo | false |
| barrier-guards.rb:199:4:199:43 | [true] ... or ... | barrier-guards.rb:200:5:200:7 | foo | true |
| barrier-guards.rb:199:20:199:31 | ... == ... | barrier-guards.rb:199:4:199:31 | [false] ... or ... | false |
| barrier-guards.rb:199:20:199:31 | ... == ... | barrier-guards.rb:199:4:199:43 | [false] ... or ... | false |
| barrier-guards.rb:199:20:199:31 | ... == ... | barrier-guards.rb:199:36:199:38 | foo | false |
| barrier-guards.rb:199:36:199:43 | ... == ... | barrier-guards.rb:199:4:199:43 | [false] ... or ... | false |
| barrier-guards.rb:203:4:203:15 | ... == ... | barrier-guards.rb:203:4:203:31 | [false] ... or ... | false |
| barrier-guards.rb:203:4:203:15 | ... == ... | barrier-guards.rb:203:4:203:47 | [false] ... or ... | false |
| barrier-guards.rb:203:4:203:15 | ... == ... | barrier-guards.rb:203:20:203:22 | self | false |
| barrier-guards.rb:203:4:203:15 | ... == ... | barrier-guards.rb:203:36:203:38 | foo | false |
| barrier-guards.rb:203:4:203:31 | [false] ... or ... | barrier-guards.rb:203:4:203:47 | [false] ... or ... | false |
| barrier-guards.rb:203:4:203:31 | [false] ... or ... | barrier-guards.rb:203:36:203:38 | foo | false |
| barrier-guards.rb:203:4:203:47 | [true] ... or ... | barrier-guards.rb:204:5:204:7 | foo | true |
| barrier-guards.rb:203:20:203:31 | ... == ... | barrier-guards.rb:203:4:203:31 | [false] ... or ... | false |
| barrier-guards.rb:203:20:203:31 | ... == ... | barrier-guards.rb:203:4:203:47 | [false] ... or ... | false |
| barrier-guards.rb:203:20:203:31 | ... == ... | barrier-guards.rb:203:36:203:38 | foo | false |
| barrier-guards.rb:203:36:203:47 | ... == ... | barrier-guards.rb:203:4:203:47 | [false] ... or ... | false |
| barrier-guards.rb:207:4:207:15 | ... == ... | barrier-guards.rb:207:4:207:21 | [true] ... and ... | true |
| barrier-guards.rb:207:4:207:15 | ... == ... | barrier-guards.rb:207:21:207:21 | self | true |
| barrier-guards.rb:207:4:207:15 | ... == ... | barrier-guards.rb:208:5:208:7 | foo | true |
| barrier-guards.rb:207:4:207:21 | [true] ... and ... | barrier-guards.rb:208:5:208:7 | foo | true |
| barrier-guards.rb:207:21:207:21 | call to x | barrier-guards.rb:207:4:207:21 | [true] ... and ... | true |
| barrier-guards.rb:207:21:207:21 | call to x | barrier-guards.rb:208:5:208:7 | foo | true |
| barrier-guards.rb:211:4:211:4 | call to x | barrier-guards.rb:211:4:211:21 | [true] ... and ... | true |
| barrier-guards.rb:211:4:211:4 | call to x | barrier-guards.rb:211:10:211:12 | foo | true |
| barrier-guards.rb:211:4:211:4 | call to x | barrier-guards.rb:212:5:212:7 | foo | true |
| barrier-guards.rb:211:4:211:21 | [true] ... and ... | barrier-guards.rb:212:5:212:7 | foo | true |
| barrier-guards.rb:211:10:211:21 | ... == ... | barrier-guards.rb:211:4:211:21 | [true] ... and ... | true |
| barrier-guards.rb:211:10:211:21 | ... == ... | barrier-guards.rb:212:5:212:7 | foo | true |
| barrier-guards.rb:215:4:215:4 | call to x | barrier-guards.rb:215:4:215:10 | [true] ... and ... | true |
| barrier-guards.rb:215:4:215:4 | call to x | barrier-guards.rb:215:4:215:27 | [true] ... and ... | true |
| barrier-guards.rb:215:4:215:4 | call to x | barrier-guards.rb:215:10:215:10 | self | true |
| barrier-guards.rb:215:4:215:4 | call to x | barrier-guards.rb:215:16:215:18 | foo | true |
| barrier-guards.rb:215:4:215:4 | call to x | barrier-guards.rb:216:5:216:7 | foo | true |
| barrier-guards.rb:215:4:215:10 | [true] ... and ... | barrier-guards.rb:215:4:215:27 | [true] ... and ... | true |
| barrier-guards.rb:215:4:215:10 | [true] ... and ... | barrier-guards.rb:215:16:215:18 | foo | true |
| barrier-guards.rb:215:4:215:10 | [true] ... and ... | barrier-guards.rb:216:5:216:7 | foo | true |
| barrier-guards.rb:215:4:215:27 | [true] ... and ... | barrier-guards.rb:216:5:216:7 | foo | true |
| barrier-guards.rb:215:10:215:10 | call to y | barrier-guards.rb:215:4:215:10 | [true] ... and ... | true |
| barrier-guards.rb:215:10:215:10 | call to y | barrier-guards.rb:215:4:215:27 | [true] ... and ... | true |
| barrier-guards.rb:215:10:215:10 | call to y | barrier-guards.rb:215:16:215:18 | foo | true |
| barrier-guards.rb:215:10:215:10 | call to y | barrier-guards.rb:216:5:216:7 | foo | true |
| barrier-guards.rb:215:16:215:27 | ... == ... | barrier-guards.rb:215:4:215:27 | [true] ... and ... | true |
| barrier-guards.rb:215:16:215:27 | ... == ... | barrier-guards.rb:216:5:216:7 | foo | true |
| barrier-guards.rb:219:4:219:15 | ... == ... | barrier-guards.rb:219:4:219:32 | [true] ... and ... | true |
| barrier-guards.rb:219:4:219:15 | ... == ... | barrier-guards.rb:219:21:219:23 | foo | true |
| barrier-guards.rb:219:4:219:15 | ... == ... | barrier-guards.rb:220:5:220:7 | foo | true |
| barrier-guards.rb:219:4:219:32 | [true] ... and ... | barrier-guards.rb:220:5:220:7 | foo | true |
| barrier-guards.rb:219:21:219:32 | ... == ... | barrier-guards.rb:219:4:219:32 | [true] ... and ... | true |
| barrier-guards.rb:219:21:219:32 | ... == ... | barrier-guards.rb:220:5:220:7 | foo | true |
| barrier-guards.rb:223:4:223:4 | call to x | barrier-guards.rb:223:4:223:10 | [true] ... and ... | true |
| barrier-guards.rb:223:4:223:4 | call to x | barrier-guards.rb:223:10:223:10 | self | true |
| barrier-guards.rb:223:4:223:4 | call to x | barrier-guards.rb:224:5:224:7 | foo | true |
| barrier-guards.rb:223:4:223:10 | [true] ... and ... | barrier-guards.rb:224:5:224:7 | foo | true |
| barrier-guards.rb:223:10:223:10 | call to y | barrier-guards.rb:223:4:223:10 | [true] ... and ... | true |
| barrier-guards.rb:223:10:223:10 | call to y | barrier-guards.rb:224:5:224:7 | foo | true |
| barrier-guards.rb:227:4:227:15 | ... == ... | barrier-guards.rb:227:4:227:21 | [true] ... and ... | true |
| barrier-guards.rb:227:4:227:15 | ... == ... | barrier-guards.rb:227:21:227:21 | self | true |
| barrier-guards.rb:227:4:227:15 | ... == ... | barrier-guards.rb:228:5:228:7 | self | true |
| barrier-guards.rb:227:4:227:21 | [true] ... and ... | barrier-guards.rb:228:5:228:7 | self | true |
| barrier-guards.rb:227:21:227:21 | call to y | barrier-guards.rb:227:4:227:21 | [true] ... and ... | true |
| barrier-guards.rb:227:21:227:21 | call to y | barrier-guards.rb:228:5:228:7 | self | true |
| barrier-guards.rb:232:1:233:19 | [true] when ... | barrier-guards.rb:233:5:233:7 | foo | true |
| barrier-guards.rb:232:6:232:17 | ... == ... | barrier-guards.rb:232:1:233:19 | [false] when ... | false |
| barrier-guards.rb:232:6:232:17 | ... == ... | barrier-guards.rb:232:1:233:19 | [true] when ... | true |
| barrier-guards.rb:232:6:232:17 | ... == ... | barrier-guards.rb:233:5:233:7 | foo | true |
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:238:1:238:26 | [false] when ... | false |
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:238:1:238:26 | [true] when ... | false |
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:238:6:238:8 | self | false |
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:238:24:238:26 | foo | false |
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:239:1:239:22 | [false] when ... | false |
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:239:1:239:22 | [true] when ... | false |
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:239:6:239:8 | foo | false |
| barrier-guards.rb:237:1:237:38 | [false] when ... | barrier-guards.rb:239:20:239:22 | foo | false |
| barrier-guards.rb:237:1:237:38 | [true] when ... | barrier-guards.rb:237:24:237:26 | foo | true |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:237:1:237:38 | [false] when ... | false |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:237:1:237:38 | [true] when ... | true |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:237:24:237:26 | foo | true |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:238:1:238:26 | [false] when ... | false |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:238:1:238:26 | [true] when ... | false |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:238:6:238:8 | self | false |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:238:24:238:26 | foo | false |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:239:1:239:22 | [false] when ... | false |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:239:1:239:22 | [true] when ... | false |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:239:6:239:8 | foo | false |
| barrier-guards.rb:237:6:237:17 | ... == ... | barrier-guards.rb:239:20:239:22 | foo | false |
| barrier-guards.rb:238:1:238:26 | [false] when ... | barrier-guards.rb:239:1:239:22 | [false] when ... | false |
| barrier-guards.rb:238:1:238:26 | [false] when ... | barrier-guards.rb:239:1:239:22 | [true] when ... | false |
| barrier-guards.rb:238:1:238:26 | [false] when ... | barrier-guards.rb:239:6:239:8 | foo | false |
| barrier-guards.rb:238:1:238:26 | [false] when ... | barrier-guards.rb:239:20:239:22 | foo | false |
| barrier-guards.rb:238:1:238:26 | [true] when ... | barrier-guards.rb:238:24:238:26 | foo | true |
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:238:1:238:26 | [false] when ... | false |
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:238:1:238:26 | [true] when ... | true |
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:238:24:238:26 | foo | true |
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:239:1:239:22 | [false] when ... | false |
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:239:1:239:22 | [true] when ... | false |
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:239:6:239:8 | foo | false |
| barrier-guards.rb:238:6:238:17 | ... == ... | barrier-guards.rb:239:20:239:22 | foo | false |
| barrier-guards.rb:239:1:239:22 | [true] when ... | barrier-guards.rb:239:20:239:22 | foo | true |
| barrier-guards.rb:239:6:239:13 | ... == ... | barrier-guards.rb:239:1:239:22 | [false] when ... | false |
| barrier-guards.rb:239:6:239:13 | ... == ... | barrier-guards.rb:239:1:239:22 | [true] when ... | true |
| barrier-guards.rb:239:6:239:13 | ... == ... | barrier-guards.rb:239:20:239:22 | foo | true |
| barrier-guards.rb:243:4:243:8 | "foo" | barrier-guards.rb:244:5:244:7 | foo | match |
| barrier-guards.rb:243:4:243:8 | "foo" | barrier-guards.rb:245:1:246:7 | in ... then ... | no-match |
| barrier-guards.rb:243:4:243:8 | "foo" | barrier-guards.rb:246:5:246:7 | foo | no-match |
| barrier-guards.rb:245:4:245:4 | x | barrier-guards.rb:246:5:246:7 | foo | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:251:5:251:7 | foo | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:254:1:256:3 | if ... | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:255:5:255:7 | foo | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:259:1:261:3 | if ... | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:260:5:260:7 | foo | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:264:1:266:3 | if ... | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:265:5:265:7 | foo | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:268:1:268:19 | ... && ... | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:268:17:268:19 | foo | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:269:1:269:19 | ... && ... | match |
| barrier-guards.rb:250:4:250:8 | "foo" | barrier-guards.rb:269:8:269:10 | foo | match |
| barrier-guards.rb:254:4:254:28 | ... == ... | barrier-guards.rb:255:5:255:7 | foo | true |
| barrier-guards.rb:259:4:259:16 | ... == ... | barrier-guards.rb:260:5:260:7 | foo | true |
| barrier-guards.rb:264:4:264:16 | ... == ... | barrier-guards.rb:265:5:265:7 | foo | true |
| barrier-guards.rb:268:1:268:12 | ... == ... | barrier-guards.rb:268:17:268:19 | foo | true |
| barrier-guards.rb:269:1:269:3 | foo | barrier-guards.rb:269:8:269:10 | foo | true |

View File

@@ -4,6 +4,7 @@ import codeql.ruby.controlflow.CfgNodes
import codeql.ruby.controlflow.ControlFlowGraph
import codeql.ruby.controlflow.BasicBlocks
import codeql.ruby.DataFlow
import TestUtilities.InlineExpectationsTest
query predicate oldStyleBarrierGuards(
BarrierGuard g, DataFlow::Node guardedNode, ExprCfgNode expr, boolean branch
@@ -22,3 +23,19 @@ query predicate controls(CfgNode condition, BasicBlock bb, SuccessorTypes::Condi
condition = cb.getLastNode()
)
}
class BarrierGuardTest extends InlineExpectationsTest {
BarrierGuardTest() { this = "BarrierGuardTest" }
override string getARelevantTag() { result = "guarded" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "guarded" and
exists(DataFlow::Node n |
newStyleBarrierGuards(n) and
location = n.getLocation() and
element = n.toString() and
value = ""
)
}
}

View File

@@ -1,13 +1,13 @@
foo = "foo"
if foo == "foo"
foo
foo # $ guarded
else
foo
end
if ["foo"].include?(foo)
foo
foo # $ guarded
else
foo
end
@@ -15,17 +15,17 @@ end
if foo != "foo"
foo
else
foo
foo # $ guarded
end
unless foo == "foo"
foo
else
foo
foo # $ guarded
end
unless foo != "foo"
foo
foo # $ guarded
else
foo
end
@@ -35,14 +35,14 @@ foo
FOO = ["foo"]
if FOO.include?(foo)
foo
foo # $ guarded
else
foo
end
if foo == "foo"
capture {
foo # guarded
foo # $ guarded
}
end
@@ -68,7 +68,7 @@ foos = ["foo"]
bars = NotAnArray.new
if foos.include?(foo)
foo
foo # $ guarded
else
foo
end
@@ -80,7 +80,7 @@ else
end
if foos.index(foo) != nil
foo
foo # $ guarded
else
foo
end
@@ -88,7 +88,7 @@ end
if foos.index(foo) == nil
foo
else
foo
foo # $ guarded
end
bars = ["bar"]
@@ -120,3 +120,150 @@ if not x then
else
bars
end
case foo
when "foo"
foo # $ guarded
else
foo
end
case foo
when "foo"
foo # $ guarded
when "bar"
foo # $ guarded
end
case foo
when "foo", "bar"
foo # $ guarded
when "baz", "quux"
foo # $ guarded
else
foo
end
case foo
when *["foo", "bar"]
foo # $ guarded
end
case foo
when *%w[foo bar]
foo # $ guarded
end
case foo
when *FOO
foo # $ guarded
end
case foo
when *foos
foo # $ guarded
end
case foo
when *["foo", x] # not a guard - includes non-constant element `x`
foo
end
case foo
when "foo", x # not a guard - includes non-constant element `x`
foo
end
foo_and_x = ["foo", x]
case foo
when *foo_and_x # not a guard - includes non-constant element `x`
foo
end
FOO_AND_X = ["foo", x]
case foo
when *FOO_AND_X # not a guard - includes non-constant element `x`
foo
end
if foo == "foo" or foo == "bar"
foo # $ guarded
end
if foo == "foo" or foo == "bar" or foo == "baz"
foo # $ guarded
end
if foo == "foo" or foo == "bar" or foo == x
foo
end
if foo == "foo" or bar == "bar" or foo == "baz"
foo
end
if foo == "foo" and x
foo # $ guarded
end
if x and foo == "foo"
foo # $ guarded
end
if x and y and foo == "foo"
foo # $ guarded
end
if foo == "foo" and foo == "bar" # $ guarded (second `foo` is guarded by the first comparison)
foo # $ guarded
end
if x and y
foo
end
if foo == "foo" and y
bar
end
case
when foo == "foo"
foo # $ guarded
end
case
when foo == "foo" then foo # $ guarded
when bar == "bar" then foo
when foo == x then foo
end
case foo
in "foo"
foo # $ guarded
in x
foo
end
case bar
in "foo"
foo
end
if foo == "#{some_method()}"
foo
end
F = "foo"
if foo == "#{F}"
foo # $ MISSING: guarded
end
f = "foo"
if foo == "#{f}"
foo # $ MISSING: guarded
end
foo == "foo" && foo # $ guarded
foo && foo == "foo"

View File

@@ -1,6 +1,6 @@
| local_dataflow.rb:1:1:7:3 | self (foo) | local_dataflow.rb:3:8:3:10 | self |
| local_dataflow.rb:1:1:7:3 | self in foo | local_dataflow.rb:1:1:7:3 | self (foo) |
| local_dataflow.rb:1:1:124:4 | self (local_dataflow.rb) | local_dataflow.rb:49:1:53:3 | self |
| local_dataflow.rb:1:1:150:3 | self (local_dataflow.rb) | local_dataflow.rb:49:1:53:3 | self |
| local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:1:9:1:9 | a |
| local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:2:7:2:7 | a |
| local_dataflow.rb:2:3:2:7 | ... = ... | local_dataflow.rb:3:13:3:13 | b |
@@ -279,3 +279,110 @@
| local_dataflow.rb:123:8:123:20 | call to dup | local_dataflow.rb:123:8:123:45 | call to tap |
| local_dataflow.rb:123:8:123:45 | call to tap | local_dataflow.rb:123:8:123:49 | call to dup |
| local_dataflow.rb:123:26:123:45 | <captured> self | local_dataflow.rb:123:32:123:43 | self |
| local_dataflow.rb:126:1:128:3 | self (use) | local_dataflow.rb:127:3:127:8 | self |
| local_dataflow.rb:126:1:128:3 | self in use | local_dataflow.rb:126:1:128:3 | self (use) |
| local_dataflow.rb:130:1:150:3 | self (use_use_madness) | local_dataflow.rb:132:6:132:11 | self |
| local_dataflow.rb:130:1:150:3 | self in use_use_madness | local_dataflow.rb:130:1:150:3 | self (use_use_madness) |
| local_dataflow.rb:131:3:131:8 | ... = ... | local_dataflow.rb:132:10:132:10 | x |
| local_dataflow.rb:131:7:131:8 | "" | local_dataflow.rb:131:3:131:8 | ... = ... |
| local_dataflow.rb:131:7:131:8 | "" | local_dataflow.rb:131:3:131:8 | ... = ... |
| local_dataflow.rb:132:6:132:11 | [post] self | local_dataflow.rb:133:8:133:13 | self |
| local_dataflow.rb:132:6:132:11 | self | local_dataflow.rb:133:8:133:13 | self |
| local_dataflow.rb:132:10:132:10 | x | local_dataflow.rb:133:12:133:12 | x |
| local_dataflow.rb:132:12:148:10 | then ... | local_dataflow.rb:132:3:149:5 | if ... |
| local_dataflow.rb:133:8:133:13 | [post] self | local_dataflow.rb:133:18:133:23 | self |
| local_dataflow.rb:133:8:133:13 | [post] self | local_dataflow.rb:134:7:134:12 | self |
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... |
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... |
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:133:18:133:23 | self |
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:134:7:134:12 | self |
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:133:22:133:22 | x |
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:134:11:134:11 | x |
| local_dataflow.rb:133:18:133:23 | [post] self | local_dataflow.rb:134:7:134:12 | self |
| local_dataflow.rb:133:18:133:23 | [post] self | local_dataflow.rb:136:7:136:12 | self |
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... |
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... |
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:134:7:134:12 | self |
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:136:7:136:12 | self |
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:134:11:134:11 | x |
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:136:11:136:11 | x |
| local_dataflow.rb:133:24:134:12 | then ... | local_dataflow.rb:133:5:139:7 | if ... |
| local_dataflow.rb:134:7:134:12 | [post] self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:134:7:134:12 | call to use | local_dataflow.rb:133:24:134:12 | then ... |
| local_dataflow.rb:134:7:134:12 | self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:134:11:134:11 | x | local_dataflow.rb:141:13:141:13 | x |
| local_dataflow.rb:135:5:138:9 | else ... | local_dataflow.rb:133:5:139:7 | if ... |
| local_dataflow.rb:136:7:136:12 | [post] self | local_dataflow.rb:137:10:137:15 | self |
| local_dataflow.rb:136:7:136:12 | self | local_dataflow.rb:137:10:137:15 | self |
| local_dataflow.rb:136:11:136:11 | x | local_dataflow.rb:137:14:137:14 | x |
| local_dataflow.rb:137:7:138:9 | if ... | local_dataflow.rb:135:5:138:9 | else ... |
| local_dataflow.rb:137:10:137:15 | [post] self | local_dataflow.rb:137:21:137:26 | self |
| local_dataflow.rb:137:10:137:15 | [post] self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [false] ... && ... |
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [true] ... && ... |
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:137:21:137:26 | self |
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:137:25:137:25 | x |
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:141:13:141:13 | x |
| local_dataflow.rb:137:20:137:26 | [false] ! ... | local_dataflow.rb:137:10:137:26 | [false] ... && ... |
| local_dataflow.rb:137:20:137:26 | [true] ! ... | local_dataflow.rb:137:10:137:26 | [true] ... && ... |
| local_dataflow.rb:137:21:137:26 | [post] self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:137:21:137:26 | self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:137:25:137:25 | x | local_dataflow.rb:141:13:141:13 | x |
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... |
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
| local_dataflow.rb:141:8:141:14 | [true] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
| local_dataflow.rb:141:9:141:14 | [post] self | local_dataflow.rb:141:20:141:25 | self |
| local_dataflow.rb:141:9:141:14 | [post] self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:141:20:141:25 | self |
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:141:24:141:24 | x |
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:147:9:147:9 | x |
| local_dataflow.rb:141:19:141:37 | [false] ( ... ) | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... |
| local_dataflow.rb:141:19:141:37 | [true] ( ... ) | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
| local_dataflow.rb:141:20:141:25 | [post] self | local_dataflow.rb:141:31:141:36 | self |
| local_dataflow.rb:141:20:141:25 | [post] self | local_dataflow.rb:143:11:143:16 | self |
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [false] ... && ... |
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [true] ... && ... |
| local_dataflow.rb:141:20:141:25 | self | local_dataflow.rb:141:31:141:36 | self |
| local_dataflow.rb:141:20:141:25 | self | local_dataflow.rb:143:11:143:16 | self |
| local_dataflow.rb:141:20:141:36 | [false] ... && ... | local_dataflow.rb:141:19:141:37 | [false] ( ... ) |
| local_dataflow.rb:141:20:141:36 | [true] ... && ... | local_dataflow.rb:141:19:141:37 | [true] ( ... ) |
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:141:35:141:35 | x |
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:143:15:143:15 | x |
| local_dataflow.rb:141:30:141:36 | [false] ! ... | local_dataflow.rb:141:20:141:36 | [false] ... && ... |
| local_dataflow.rb:141:30:141:36 | [true] ! ... | local_dataflow.rb:141:20:141:36 | [true] ... && ... |
| local_dataflow.rb:141:31:141:36 | [post] self | local_dataflow.rb:143:11:143:16 | self |
| local_dataflow.rb:141:31:141:36 | [post] self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:141:31:141:36 | self | local_dataflow.rb:143:11:143:16 | self |
| local_dataflow.rb:141:31:141:36 | self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:141:35:141:35 | x | local_dataflow.rb:143:15:143:15 | x |
| local_dataflow.rb:141:35:141:35 | x | local_dataflow.rb:147:9:147:9 | x |
| local_dataflow.rb:141:38:142:9 | then ... | local_dataflow.rb:141:5:145:7 | if ... |
| local_dataflow.rb:142:7:142:9 | nil | local_dataflow.rb:141:38:142:9 | then ... |
| local_dataflow.rb:143:5:144:16 | elsif ... | local_dataflow.rb:141:5:145:7 | if ... |
| local_dataflow.rb:143:11:143:16 | [post] self | local_dataflow.rb:143:21:143:26 | self |
| local_dataflow.rb:143:11:143:16 | [post] self | local_dataflow.rb:144:11:144:16 | self |
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... |
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... |
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:143:21:143:26 | self |
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:144:11:144:16 | self |
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:143:25:143:25 | x |
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:144:15:144:15 | x |
| local_dataflow.rb:143:21:143:26 | [post] self | local_dataflow.rb:144:11:144:16 | self |
| local_dataflow.rb:143:21:143:26 | [post] self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... |
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... |
| local_dataflow.rb:143:21:143:26 | self | local_dataflow.rb:144:11:144:16 | self |
| local_dataflow.rb:143:21:143:26 | self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:143:25:143:25 | x | local_dataflow.rb:144:15:144:15 | x |
| local_dataflow.rb:143:25:143:25 | x | local_dataflow.rb:147:9:147:9 | x |
| local_dataflow.rb:143:27:144:16 | then ... | local_dataflow.rb:143:5:144:16 | elsif ... |
| local_dataflow.rb:144:11:144:16 | [post] self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:144:11:144:16 | call to use | local_dataflow.rb:143:27:144:16 | then ... |
| local_dataflow.rb:144:11:144:16 | self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:144:15:144:15 | x | local_dataflow.rb:147:9:147:9 | x |
| local_dataflow.rb:147:5:147:10 | [post] self | local_dataflow.rb:148:5:148:10 | self |
| local_dataflow.rb:147:5:147:10 | self | local_dataflow.rb:148:5:148:10 | self |
| local_dataflow.rb:147:9:147:9 | x | local_dataflow.rb:148:9:148:9 | x |
| local_dataflow.rb:148:5:148:10 | call to use | local_dataflow.rb:132:12:148:10 | then ... |

View File

@@ -19,6 +19,8 @@ ret
| local_dataflow.rb:119:3:119:31 | call to sink |
| local_dataflow.rb:123:3:123:50 | call to sink |
| local_dataflow.rb:123:32:123:43 | call to puts |
| local_dataflow.rb:127:3:127:8 | call to rand |
| local_dataflow.rb:132:3:149:5 | if ... |
arg
| local_dataflow.rb:3:8:3:10 | self | local_dataflow.rb:3:8:3:10 | call to p | self |
| local_dataflow.rb:3:10:3:10 | a | local_dataflow.rb:3:8:3:10 | call to p | position 0 |
@@ -170,3 +172,61 @@ arg
| local_dataflow.rb:123:26:123:45 | { ... } | local_dataflow.rb:123:8:123:45 | call to tap | block |
| local_dataflow.rb:123:32:123:43 | self | local_dataflow.rb:123:32:123:43 | call to puts | self |
| local_dataflow.rb:123:37:123:43 | "hello" | local_dataflow.rb:123:32:123:43 | call to puts | position 0 |
| local_dataflow.rb:127:3:127:8 | self | local_dataflow.rb:127:3:127:8 | call to rand | self |
| local_dataflow.rb:132:6:132:11 | self | local_dataflow.rb:132:6:132:11 | call to use | self |
| local_dataflow.rb:132:10:132:10 | x | local_dataflow.rb:132:6:132:11 | call to use | position 0 |
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... | self |
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... | self |
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:133:8:133:13 | call to use | self |
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:133:8:133:13 | call to use | position 0 |
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... | position 0 |
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... | position 0 |
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:133:18:133:23 | call to use | self |
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:133:18:133:23 | call to use | position 0 |
| local_dataflow.rb:134:7:134:12 | self | local_dataflow.rb:134:7:134:12 | call to use | self |
| local_dataflow.rb:134:11:134:11 | x | local_dataflow.rb:134:7:134:12 | call to use | position 0 |
| local_dataflow.rb:136:7:136:12 | self | local_dataflow.rb:136:7:136:12 | call to use | self |
| local_dataflow.rb:136:11:136:11 | x | local_dataflow.rb:136:7:136:12 | call to use | position 0 |
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [false] ... && ... | self |
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [true] ... && ... | self |
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:137:10:137:15 | call to use | self |
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:137:10:137:15 | call to use | position 0 |
| local_dataflow.rb:137:20:137:26 | [false] ! ... | local_dataflow.rb:137:10:137:26 | [false] ... && ... | position 0 |
| local_dataflow.rb:137:20:137:26 | [true] ! ... | local_dataflow.rb:137:10:137:26 | [true] ... && ... | position 0 |
| local_dataflow.rb:137:21:137:26 | call to use | local_dataflow.rb:137:20:137:26 | [false] ! ... | self |
| local_dataflow.rb:137:21:137:26 | call to use | local_dataflow.rb:137:20:137:26 | [true] ! ... | self |
| local_dataflow.rb:137:21:137:26 | self | local_dataflow.rb:137:21:137:26 | call to use | self |
| local_dataflow.rb:137:25:137:25 | x | local_dataflow.rb:137:21:137:26 | call to use | position 0 |
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... | self |
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... | self |
| local_dataflow.rb:141:8:141:14 | [true] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... | self |
| local_dataflow.rb:141:9:141:14 | call to use | local_dataflow.rb:141:8:141:14 | [false] ! ... | self |
| local_dataflow.rb:141:9:141:14 | call to use | local_dataflow.rb:141:8:141:14 | [true] ! ... | self |
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:141:9:141:14 | call to use | self |
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:141:9:141:14 | call to use | position 0 |
| local_dataflow.rb:141:19:141:37 | [false] ( ... ) | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... | position 0 |
| local_dataflow.rb:141:19:141:37 | [true] ( ... ) | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... | position 0 |
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [false] ... && ... | self |
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [true] ... && ... | self |
| local_dataflow.rb:141:20:141:25 | self | local_dataflow.rb:141:20:141:25 | call to use | self |
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:141:20:141:25 | call to use | position 0 |
| local_dataflow.rb:141:30:141:36 | [false] ! ... | local_dataflow.rb:141:20:141:36 | [false] ... && ... | position 0 |
| local_dataflow.rb:141:30:141:36 | [true] ! ... | local_dataflow.rb:141:20:141:36 | [true] ... && ... | position 0 |
| local_dataflow.rb:141:31:141:36 | call to use | local_dataflow.rb:141:30:141:36 | [false] ! ... | self |
| local_dataflow.rb:141:31:141:36 | call to use | local_dataflow.rb:141:30:141:36 | [true] ! ... | self |
| local_dataflow.rb:141:31:141:36 | self | local_dataflow.rb:141:31:141:36 | call to use | self |
| local_dataflow.rb:141:35:141:35 | x | local_dataflow.rb:141:31:141:36 | call to use | position 0 |
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... | self |
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... | self |
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:143:11:143:16 | call to use | self |
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:143:11:143:16 | call to use | position 0 |
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... | position 0 |
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... | position 0 |
| local_dataflow.rb:143:21:143:26 | self | local_dataflow.rb:143:21:143:26 | call to use | self |
| local_dataflow.rb:143:25:143:25 | x | local_dataflow.rb:143:21:143:26 | call to use | position 0 |
| local_dataflow.rb:144:11:144:16 | self | local_dataflow.rb:144:11:144:16 | call to use | self |
| local_dataflow.rb:144:15:144:15 | x | local_dataflow.rb:144:11:144:16 | call to use | position 0 |
| local_dataflow.rb:147:5:147:10 | self | local_dataflow.rb:147:5:147:10 | call to use | self |
| local_dataflow.rb:147:9:147:9 | x | local_dataflow.rb:147:5:147:10 | call to use | position 0 |
| local_dataflow.rb:148:5:148:10 | self | local_dataflow.rb:148:5:148:10 | call to use | self |
| local_dataflow.rb:148:9:148:9 | x | local_dataflow.rb:148:5:148:10 | call to use | position 0 |

View File

@@ -1,9 +1,6 @@
| file://:0:0:0:0 | [summary] read: argument position 0.any element in Hash[] | file://:0:0:0:0 | [summary] read: argument position 0.any element.element 1 or unknown in Hash[] |
| file://:0:0:0:0 | parameter any of ;Pathname;Method[join] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[join] |
| 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 0 of + | file://:0:0:0:0 | [summary] read: argument position 0.any element in + |
| file://:0:0:0:0 | parameter position 0 of ;;Member[Pathname].Method[new] | file://:0:0:0:0 | [summary] to write: return (return) in ;;Member[Pathname].Method[new] |
| file://:0:0:0:0 | parameter position 0 of ;;Member[Regexp].Method[escape,quote] | file://:0:0:0:0 | [summary] to write: return (return) in ;;Member[Regexp].Method[escape,quote] |
| file://:0:0:0:0 | parameter position 0 of ActionController::Parameters#merge | file://:0:0:0:0 | [summary] to write: return (return) in ActionController::Parameters#merge |
| file://:0:0:0:0 | parameter position 0 of ActionController::Parameters#merge! | file://:0:0:0:0 | [summary] to write: argument self in ActionController::Parameters#merge! |
| file://:0:0:0:0 | parameter position 0 of ActionController::Parameters#merge! | file://:0:0:0:0 | [summary] to write: return (return) in ActionController::Parameters#merge! |
@@ -17,27 +14,10 @@
| 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 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 0 of activestorage;;Member[ActiveStorage].Member[Filename].Method[new] | file://:0:0:0:0 | [summary] to write: return (return) in activestorage;;Member[ActiveStorage].Member[Filename].Method[new] |
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] | file://:0:0:0:0 | [summary] to write: argument self in activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] |
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] | file://:0:0:0:0 | [summary] to write: return (return) in activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[safe_concat] |
| file://:0:0:0:0 | parameter position 0 of activesupport;;Member[ActionView].Member[SafeBuffer].Method[new] | file://:0:0:0:0 | [summary] to write: return (return) in activesupport;;Member[ActionView].Member[SafeBuffer].Method[new] |
| file://:0:0:0:0 | parameter position 0.. of File.join | file://:0:0:0:0 | [summary] to write: return (return) in File.join |
| file://:0:0:0:0 | parameter self of & | file://:0:0:0:0 | [summary] read: argument self.any element in & |
| file://:0:0:0:0 | parameter self of * | file://:0:0:0:0 | [summary] read: argument self.any element in * |
| file://:0:0:0:0 | parameter self of - | file://:0:0:0:0 | [summary] read: argument self.any element in - |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[basename] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[basename] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[cleanpath] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[cleanpath] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[dirname] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[dirname] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[each_filename] | file://:0:0:0:0 | [summary] to write: argument block.parameter position 0 in ;Pathname;Method[each_filename] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[existence] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[existence] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[expand_path] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[expand_path] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[join] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[join] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[parent] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[parent] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[realpath] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[realpath] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[relative_path_from] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[relative_path_from] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[sub] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[sub] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[sub_ext] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[sub_ext] |
| file://:0:0:0:0 | parameter self of ;Pathname;Method[to_path] | file://:0:0:0:0 | [summary] to write: return (return) in ;Pathname;Method[to_path] |
| file://:0:0:0:0 | parameter self of ActionController::Parameters#<various> | file://:0:0:0:0 | [summary] to write: return (return) in ActionController::Parameters#<various> |
| file://:0:0:0:0 | parameter self of ActionController::Parameters#merge | file://:0:0:0:0 | [summary] to write: return (return) in ActionController::Parameters#merge |
| file://:0:0:0:0 | parameter self of ActionController::Parameters#merge! | file://:0:0:0:0 | [summary] to write: argument self in ActionController::Parameters#merge! |
@@ -45,12 +25,10 @@
| file://:0:0:0:0 | parameter self of ActiveSupportStringTransform | file://:0:0:0:0 | [summary] to write: return (return) in ActiveSupportStringTransform |
| file://:0:0:0:0 | parameter self of [] | file://:0:0:0:0 | [summary] to write: return (return) in [] |
| file://:0:0:0:0 | parameter self of \| | file://:0:0:0:0 | [summary] read: argument self.any element in \| |
| file://:0:0:0:0 | parameter self of activestorage;;Member[ActiveStorage].Member[Filename].Instance.Method[sanitized] | file://:0:0:0:0 | [summary] to write: return (return) in activestorage;;Member[ActiveStorage].Member[Filename].Instance.Method[sanitized] |
| file://:0:0:0:0 | parameter self of activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[concat,insert,prepend,to_s,to_param] | file://:0:0:0:0 | [summary] to write: return (return) in activesupport;;Member[ActionView].Member[SafeBuffer].Instance.Method[concat,insert,prepend,to_s,to_param] |
| file://:0:0:0:0 | parameter self of each(0) | file://:0:0:0:0 | [summary] read: argument self.any element in each(0) |
| local_dataflow.rb:1:1:7:3 | self (foo) | local_dataflow.rb:3:8:3:10 | self |
| local_dataflow.rb:1:1:7:3 | self in foo | local_dataflow.rb:1:1:7:3 | self (foo) |
| local_dataflow.rb:1:1:124:4 | self (local_dataflow.rb) | local_dataflow.rb:49:1:53:3 | self |
| local_dataflow.rb:1:1:150:3 | self (local_dataflow.rb) | local_dataflow.rb:49:1:53:3 | self |
| local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:1:9:1:9 | a |
| local_dataflow.rb:1:9:1:9 | a | local_dataflow.rb:2:7:2:7 | a |
| local_dataflow.rb:2:3:2:7 | ... = ... | local_dataflow.rb:3:13:3:13 | b |
@@ -354,3 +332,116 @@
| local_dataflow.rb:123:8:123:20 | call to dup | local_dataflow.rb:123:8:123:45 | call to tap |
| local_dataflow.rb:123:8:123:45 | call to tap | local_dataflow.rb:123:8:123:49 | call to dup |
| local_dataflow.rb:123:26:123:45 | <captured> self | local_dataflow.rb:123:32:123:43 | self |
| local_dataflow.rb:126:1:128:3 | self (use) | local_dataflow.rb:127:3:127:8 | self |
| local_dataflow.rb:126:1:128:3 | self in use | local_dataflow.rb:126:1:128:3 | self (use) |
| local_dataflow.rb:130:1:150:3 | self (use_use_madness) | local_dataflow.rb:132:6:132:11 | self |
| local_dataflow.rb:130:1:150:3 | self in use_use_madness | local_dataflow.rb:130:1:150:3 | self (use_use_madness) |
| local_dataflow.rb:131:3:131:8 | ... = ... | local_dataflow.rb:132:10:132:10 | x |
| local_dataflow.rb:131:7:131:8 | "" | local_dataflow.rb:131:3:131:8 | ... = ... |
| local_dataflow.rb:131:7:131:8 | "" | local_dataflow.rb:131:3:131:8 | ... = ... |
| local_dataflow.rb:132:6:132:11 | [post] self | local_dataflow.rb:133:8:133:13 | self |
| local_dataflow.rb:132:6:132:11 | self | local_dataflow.rb:133:8:133:13 | self |
| local_dataflow.rb:132:10:132:10 | x | local_dataflow.rb:133:12:133:12 | x |
| local_dataflow.rb:132:12:148:10 | then ... | local_dataflow.rb:132:3:149:5 | if ... |
| local_dataflow.rb:133:8:133:13 | [post] self | local_dataflow.rb:133:18:133:23 | self |
| local_dataflow.rb:133:8:133:13 | [post] self | local_dataflow.rb:134:7:134:12 | self |
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... |
| local_dataflow.rb:133:8:133:13 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... |
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:133:18:133:23 | self |
| local_dataflow.rb:133:8:133:13 | self | local_dataflow.rb:134:7:134:12 | self |
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:133:22:133:22 | x |
| local_dataflow.rb:133:12:133:12 | x | local_dataflow.rb:134:11:134:11 | x |
| local_dataflow.rb:133:18:133:23 | [post] self | local_dataflow.rb:134:7:134:12 | self |
| local_dataflow.rb:133:18:133:23 | [post] self | local_dataflow.rb:136:7:136:12 | self |
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [false] ... \|\| ... |
| local_dataflow.rb:133:18:133:23 | call to use | local_dataflow.rb:133:8:133:23 | [true] ... \|\| ... |
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:134:7:134:12 | self |
| local_dataflow.rb:133:18:133:23 | self | local_dataflow.rb:136:7:136:12 | self |
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:134:11:134:11 | x |
| local_dataflow.rb:133:22:133:22 | x | local_dataflow.rb:136:11:136:11 | x |
| local_dataflow.rb:133:24:134:12 | then ... | local_dataflow.rb:133:5:139:7 | if ... |
| local_dataflow.rb:134:7:134:12 | [post] self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:134:7:134:12 | call to use | local_dataflow.rb:133:24:134:12 | then ... |
| local_dataflow.rb:134:7:134:12 | self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:134:11:134:11 | x | local_dataflow.rb:141:13:141:13 | x |
| local_dataflow.rb:135:5:138:9 | else ... | local_dataflow.rb:133:5:139:7 | if ... |
| local_dataflow.rb:136:7:136:12 | [post] self | local_dataflow.rb:137:10:137:15 | self |
| local_dataflow.rb:136:7:136:12 | self | local_dataflow.rb:137:10:137:15 | self |
| local_dataflow.rb:136:11:136:11 | x | local_dataflow.rb:137:14:137:14 | x |
| local_dataflow.rb:137:7:138:9 | if ... | local_dataflow.rb:135:5:138:9 | else ... |
| local_dataflow.rb:137:10:137:15 | [post] self | local_dataflow.rb:137:21:137:26 | self |
| local_dataflow.rb:137:10:137:15 | [post] self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [false] ... && ... |
| local_dataflow.rb:137:10:137:15 | call to use | local_dataflow.rb:137:10:137:26 | [true] ... && ... |
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:137:21:137:26 | self |
| local_dataflow.rb:137:10:137:15 | self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:137:25:137:25 | x |
| local_dataflow.rb:137:14:137:14 | x | local_dataflow.rb:141:13:141:13 | x |
| local_dataflow.rb:137:20:137:26 | [false] ! ... | local_dataflow.rb:137:10:137:26 | [false] ... && ... |
| local_dataflow.rb:137:20:137:26 | [true] ! ... | local_dataflow.rb:137:10:137:26 | [true] ... && ... |
| local_dataflow.rb:137:21:137:26 | [post] self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:137:21:137:26 | call to use | local_dataflow.rb:137:20:137:26 | [false] ! ... |
| local_dataflow.rb:137:21:137:26 | call to use | local_dataflow.rb:137:20:137:26 | [true] ! ... |
| local_dataflow.rb:137:21:137:26 | self | local_dataflow.rb:141:9:141:14 | self |
| local_dataflow.rb:137:25:137:25 | x | local_dataflow.rb:141:13:141:13 | x |
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... |
| local_dataflow.rb:141:8:141:14 | [false] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
| local_dataflow.rb:141:8:141:14 | [true] ! ... | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
| local_dataflow.rb:141:9:141:14 | [post] self | local_dataflow.rb:141:20:141:25 | self |
| local_dataflow.rb:141:9:141:14 | [post] self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:141:9:141:14 | call to use | local_dataflow.rb:141:8:141:14 | [false] ! ... |
| local_dataflow.rb:141:9:141:14 | call to use | local_dataflow.rb:141:8:141:14 | [true] ! ... |
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:141:20:141:25 | self |
| local_dataflow.rb:141:9:141:14 | self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:141:24:141:24 | x |
| local_dataflow.rb:141:13:141:13 | x | local_dataflow.rb:147:9:147:9 | x |
| local_dataflow.rb:141:19:141:37 | [false] ( ... ) | local_dataflow.rb:141:8:141:37 | [false] ... \|\| ... |
| local_dataflow.rb:141:19:141:37 | [true] ( ... ) | local_dataflow.rb:141:8:141:37 | [true] ... \|\| ... |
| local_dataflow.rb:141:20:141:25 | [post] self | local_dataflow.rb:141:31:141:36 | self |
| local_dataflow.rb:141:20:141:25 | [post] self | local_dataflow.rb:143:11:143:16 | self |
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [false] ... && ... |
| local_dataflow.rb:141:20:141:25 | call to use | local_dataflow.rb:141:20:141:36 | [true] ... && ... |
| local_dataflow.rb:141:20:141:25 | self | local_dataflow.rb:141:31:141:36 | self |
| local_dataflow.rb:141:20:141:25 | self | local_dataflow.rb:143:11:143:16 | self |
| local_dataflow.rb:141:20:141:36 | [false] ... && ... | local_dataflow.rb:141:19:141:37 | [false] ( ... ) |
| local_dataflow.rb:141:20:141:36 | [true] ... && ... | local_dataflow.rb:141:19:141:37 | [true] ( ... ) |
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:141:35:141:35 | x |
| local_dataflow.rb:141:24:141:24 | x | local_dataflow.rb:143:15:143:15 | x |
| local_dataflow.rb:141:30:141:36 | [false] ! ... | local_dataflow.rb:141:20:141:36 | [false] ... && ... |
| local_dataflow.rb:141:30:141:36 | [true] ! ... | local_dataflow.rb:141:20:141:36 | [true] ... && ... |
| local_dataflow.rb:141:31:141:36 | [post] self | local_dataflow.rb:143:11:143:16 | self |
| local_dataflow.rb:141:31:141:36 | [post] self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:141:31:141:36 | call to use | local_dataflow.rb:141:30:141:36 | [false] ! ... |
| local_dataflow.rb:141:31:141:36 | call to use | local_dataflow.rb:141:30:141:36 | [true] ! ... |
| local_dataflow.rb:141:31:141:36 | self | local_dataflow.rb:143:11:143:16 | self |
| local_dataflow.rb:141:31:141:36 | self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:141:35:141:35 | x | local_dataflow.rb:143:15:143:15 | x |
| local_dataflow.rb:141:35:141:35 | x | local_dataflow.rb:147:9:147:9 | x |
| local_dataflow.rb:141:38:142:9 | then ... | local_dataflow.rb:141:5:145:7 | if ... |
| local_dataflow.rb:142:7:142:9 | nil | local_dataflow.rb:141:38:142:9 | then ... |
| local_dataflow.rb:143:5:144:16 | elsif ... | local_dataflow.rb:141:5:145:7 | if ... |
| local_dataflow.rb:143:11:143:16 | [post] self | local_dataflow.rb:143:21:143:26 | self |
| local_dataflow.rb:143:11:143:16 | [post] self | local_dataflow.rb:144:11:144:16 | self |
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... |
| local_dataflow.rb:143:11:143:16 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... |
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:143:21:143:26 | self |
| local_dataflow.rb:143:11:143:16 | self | local_dataflow.rb:144:11:144:16 | self |
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:143:25:143:25 | x |
| local_dataflow.rb:143:15:143:15 | x | local_dataflow.rb:144:15:144:15 | x |
| local_dataflow.rb:143:21:143:26 | [post] self | local_dataflow.rb:144:11:144:16 | self |
| local_dataflow.rb:143:21:143:26 | [post] self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [false] ... \|\| ... |
| local_dataflow.rb:143:21:143:26 | call to use | local_dataflow.rb:143:11:143:26 | [true] ... \|\| ... |
| local_dataflow.rb:143:21:143:26 | self | local_dataflow.rb:144:11:144:16 | self |
| local_dataflow.rb:143:21:143:26 | self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:143:25:143:25 | x | local_dataflow.rb:144:15:144:15 | x |
| local_dataflow.rb:143:25:143:25 | x | local_dataflow.rb:147:9:147:9 | x |
| local_dataflow.rb:143:27:144:16 | then ... | local_dataflow.rb:143:5:144:16 | elsif ... |
| local_dataflow.rb:144:11:144:16 | [post] self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:144:11:144:16 | call to use | local_dataflow.rb:143:27:144:16 | then ... |
| local_dataflow.rb:144:11:144:16 | self | local_dataflow.rb:147:5:147:10 | self |
| local_dataflow.rb:144:15:144:15 | x | local_dataflow.rb:147:9:147:9 | x |
| local_dataflow.rb:147:5:147:10 | [post] self | local_dataflow.rb:148:5:148:10 | self |
| local_dataflow.rb:147:5:147:10 | self | local_dataflow.rb:148:5:148:10 | self |
| local_dataflow.rb:147:9:147:9 | x | local_dataflow.rb:148:9:148:9 | x |
| local_dataflow.rb:148:5:148:10 | call to use | local_dataflow.rb:132:12:148:10 | then ... |

View File

@@ -122,3 +122,29 @@ end
def dup_tap
sink(source(1).dup.tap { |x| puts "hello" }.dup) # $ hasValueFlow=1
end
def use x
rand()
end
def use_use_madness
x = ""
if use(x)
if use(x) || use(x)
use(x)
else
use(x)
if use(x) && !use(x)
end
end
if !use(x) || (use(x) && !use(x))
nil
elsif use(x) || use(x)
use(x)
end
use(x)
use(x)
end
end

View File

@@ -40,6 +40,11 @@ edges
| params_flow.rb:49:13:49:14 | p1 : | params_flow.rb:50:10:50:11 | p1 |
| params_flow.rb:54:9:54:17 | call to taint : | params_flow.rb:49:13:49:14 | p1 : |
| params_flow.rb:57:9:57:17 | call to taint : | params_flow.rb:49:13:49:14 | p1 : |
| params_flow.rb:62:8:62:16 | call to taint : | params_flow.rb:66:13:66:16 | args : |
| params_flow.rb:63:16:63:17 | *x [element 0] : | params_flow.rb:64:10:64:10 | x [element 0] : |
| params_flow.rb:64:10:64:10 | x [element 0] : | params_flow.rb:64:10:64:13 | ...[...] |
| params_flow.rb:66:12:66:16 | * ... [element 0] : | params_flow.rb:63:16:63:17 | *x [element 0] : |
| params_flow.rb:66:13:66:16 | args : | params_flow.rb:66:12:66:16 | * ... [element 0] : |
nodes
| params_flow.rb:9:16:9:17 | p1 : | semmle.label | p1 : |
| params_flow.rb:9:20:9:21 | p2 : | semmle.label | p2 : |
@@ -89,6 +94,12 @@ nodes
| params_flow.rb:50:10:50:11 | p1 | semmle.label | p1 |
| params_flow.rb:54:9:54:17 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:57:9:57:17 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:62:8:62:16 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:63:16:63:17 | *x [element 0] : | semmle.label | *x [element 0] : |
| params_flow.rb:64:10:64:10 | x [element 0] : | semmle.label | x [element 0] : |
| params_flow.rb:64:10:64:13 | ...[...] | semmle.label | ...[...] |
| params_flow.rb:66:12:66:16 | * ... [element 0] : | semmle.label | * ... [element 0] : |
| params_flow.rb:66:13:66:16 | args : | semmle.label | args : |
subpaths
#select
| params_flow.rb:10:10:10:11 | p1 | params_flow.rb:14:12:14:19 | call to taint : | params_flow.rb:10:10:10:11 | p1 | $@ | params_flow.rb:14:12:14:19 | call to taint : | call to taint : |
@@ -111,3 +122,4 @@ subpaths
| params_flow.rb:29:10:29:22 | ( ... ) | params_flow.rb:34:14:34:22 | call to taint : | params_flow.rb:29:10:29:22 | ( ... ) | $@ | params_flow.rb:34:14:34:22 | call to taint : | call to taint : |
| params_flow.rb:50:10:50:11 | p1 | params_flow.rb:54:9:54:17 | call to taint : | params_flow.rb:50:10:50:11 | p1 | $@ | params_flow.rb:54:9:54:17 | call to taint : | call to taint : |
| params_flow.rb:50:10:50:11 | p1 | params_flow.rb:57:9:57:17 | call to taint : | params_flow.rb:50:10:50:11 | p1 | $@ | params_flow.rb:57:9:57:17 | call to taint : | call to taint : |
| params_flow.rb:64:10:64:13 | ...[...] | params_flow.rb:62:8:62:16 | call to taint : | params_flow.rb:64:10:64:13 | ...[...] | $@ | params_flow.rb:62:8:62:16 | call to taint : | call to taint : |

View File

@@ -57,4 +57,10 @@ args = [taint(22)]
posargs(taint(23), *args)
args = [taint(24), taint(25)]
posargs(*args)
posargs(*args)
args = taint(26)
def splatstuff(*x)
sink x[0] # $ hasValueFlow=26
end
splatstuff(*args)

View File

@@ -12,6 +12,7 @@ edges
| string_flow.rb:10:29:10:29 | b : | string_flow.rb:10:10:10:30 | call to try_convert |
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:15:10:15:17 | ... % ... |
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:15:17:15:17 | a : |
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:16:10:16:29 | ... % ... |
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:16:28:16:28 | a : |
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:17:10:17:10 | a : |
| string_flow.rb:14:9:14:18 | call to source : | string_flow.rb:17:10:17:18 | ... % ... |

View File

@@ -534,8 +534,8 @@ invalidSpecComponent
| summaries.rb:150:39:150:45 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:150:39:150:45 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:150:39:150:45 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:150:39:150:45 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
warning
| CSV type row should have 5 columns but has 2: test;TooFewColumns |
| CSV type row should have 5 columns but has 8: test;TooManyColumns;;;Member[Foo].Instance;too;many;columns |
| CSV type row should have 3 columns but has 1: TooFewColumns |
| CSV type row should have 3 columns but has 6: TooManyColumns;;Member[Foo].Instance;too;many;columns |
| Invalid argument '0-1' in token 'Argument[0-1]' in access path: Method[foo].Argument[0-1] |
| Invalid argument '*' in token 'Argument[*]' in access path: Method[foo].Argument[*] |
| Invalid token 'Argument' is missing its arguments, in access path: Method[foo].Argument |

View File

@@ -67,32 +67,32 @@ private class StepsFromModel extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
";any;Method[set_value];Argument[0];Argument[self].Field[@value];value",
";any;Method[get_value];Argument[self].Field[@value];ReturnValue;value",
";;Member[Foo].Method[firstArg];Argument[0];ReturnValue;taint",
";;Member[Foo].Method[secondArg];Argument[1];ReturnValue;taint",
";;Member[Foo].Method[onlyWithoutBlock].WithoutBlock;Argument[0];ReturnValue;taint",
";;Member[Foo].Method[onlyWithBlock].WithBlock;Argument[0];ReturnValue;taint",
";;Member[Foo].Method[blockArg].Argument[block].Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
";;Member[Foo].Method[namedArg];Argument[foo:];ReturnValue;taint",
";;Member[Foo].Method[anyArg];Argument[any];ReturnValue;taint",
";;Member[Foo].Method[anyNamedArg];Argument[any-named];ReturnValue;taint",
";;Member[Foo].Method[anyPositionFromOne];Argument[1..];ReturnValue;taint",
";;Member[Foo].Method[intoNamedCallback];Argument[0];Argument[foo:].Parameter[0];taint",
";;Member[Foo].Method[intoNamedParameter];Argument[0];Argument[0].Parameter[foo:];taint",
";;Member[Foo].Method[startInNamedCallback].Argument[foo:].Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
";;Member[Foo].Method[startInNamedParameter].Argument[0].Parameter[foo:].Method[preserveTaint];Argument[0];ReturnValue;taint",
";;Member[Foo].Instance.Method[flowToAnyArg];Argument[0];Argument[any];taint",
";;Member[Foo].Instance.Method[flowToSelf];Argument[0];Argument[self];taint",
";any;Method[matchedByName];Argument[0];ReturnValue;taint",
";any;Method[matchedByNameRcv];Argument[self];ReturnValue;taint",
";any;Method[withElementOne];Argument[self].WithElement[1];ReturnValue;value",
";any;Method[withExactlyElementOne];Argument[self].WithElement[1!];ReturnValue;value",
";any;Method[withoutElementOne];Argument[self].WithoutElement[1];Argument[self];value",
";any;Method[withoutExactlyElementOne];Argument[self].WithoutElement[1!];Argument[self];value",
";any;Method[readElementOne];Argument[self].Element[1];ReturnValue;value",
";any;Method[readExactlyElementOne];Argument[self].Element[1!];ReturnValue;value",
";any;Method[withoutElementOneAndTwo];Argument[self].WithoutElement[1].WithoutElement[2].WithElement[any];Argument[self];value",
"any;Method[set_value];Argument[0];Argument[self].Field[@value];value",
"any;Method[get_value];Argument[self].Field[@value];ReturnValue;value",
"Foo!;Method[firstArg];Argument[0];ReturnValue;taint",
"Foo!;Method[secondArg];Argument[1];ReturnValue;taint",
"Foo!;Method[onlyWithoutBlock].WithoutBlock;Argument[0];ReturnValue;taint",
"Foo!;Method[onlyWithBlock].WithBlock;Argument[0];ReturnValue;taint",
"Foo!;Method[blockArg].Argument[block].Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
"Foo!;Method[namedArg];Argument[foo:];ReturnValue;taint",
"Foo!;Method[anyArg];Argument[any];ReturnValue;taint",
"Foo!;Method[anyNamedArg];Argument[any-named];ReturnValue;taint",
"Foo!;Method[anyPositionFromOne];Argument[1..];ReturnValue;taint",
"Foo!;Method[intoNamedCallback];Argument[0];Argument[foo:].Parameter[0];taint",
"Foo!;Method[intoNamedParameter];Argument[0];Argument[0].Parameter[foo:];taint",
"Foo!;Method[startInNamedCallback].Argument[foo:].Parameter[0].Method[preserveTaint];Argument[0];ReturnValue;taint",
"Foo!;Method[startInNamedParameter].Argument[0].Parameter[foo:].Method[preserveTaint];Argument[0];ReturnValue;taint",
"Foo;Method[flowToAnyArg];Argument[0];Argument[any];taint",
"Foo;Method[flowToSelf];Argument[0];Argument[self];taint",
"any;Method[matchedByName];Argument[0];ReturnValue;taint",
"any;Method[matchedByNameRcv];Argument[self];ReturnValue;taint",
"any;Method[withElementOne];Argument[self].WithElement[1];ReturnValue;value",
"any;Method[withExactlyElementOne];Argument[self].WithElement[1!];ReturnValue;value",
"any;Method[withoutElementOne];Argument[self].WithoutElement[1];Argument[self];value",
"any;Method[withoutExactlyElementOne];Argument[self].WithoutElement[1!];Argument[self];value",
"any;Method[readElementOne];Argument[self].Element[1];ReturnValue;value",
"any;Method[readExactlyElementOne];Argument[self].Element[1!];ReturnValue;value",
"any;Method[withoutElementOneAndTwo];Argument[self].WithoutElement[1].WithoutElement[2].WithElement[any];Argument[self];value",
]
}
}
@@ -101,23 +101,21 @@ private class TypeFromModel extends ModelInput::TypeModelCsv {
override predicate row(string row) {
row =
[
"test;FooOrBar;;;Member[Foo].Instance", //
"test;FooOrBar;;;Member[Bar].Instance", //
"test;FooOrBar;test;FooOrBar;Method[next].ReturnValue",
"~FooOrBar;Foo;", //
"~FooOrBar;Bar;", //
"~FooOrBar;~FooOrBar;Method[next].ReturnValue",
]
}
}
private class TypeFromCodeQL extends ModelInput::TypeModel {
override DataFlow::Node getASource(string package, string type) {
package = "test" and
type = "FooOrBar" and
override DataFlow::Node getASource(string type) {
type = "~FooOrBar" and
result.getConstantValue().getString() = "magic_string"
}
override API::Node getAnApiNode(string package, string type) {
package = "test" and
type = "FooOrBar" and
override API::Node getAnApiNode(string type) {
type = "~FooOrBar" and
result = API::getTopLevelMember("Alias").getMember(["Foo", "Bar"])
}
}
@@ -126,13 +124,13 @@ private class InvalidTypeModel extends ModelInput::TypeModelCsv {
override predicate row(string row) {
row =
[
"test;TooManyColumns;;;Member[Foo].Instance;too;many;columns", //
"test;TooFewColumns", //
"test;X;test;Y;Method[foo].Arg[0]", //
"test;X;test;Y;Method[foo].Argument[0-1]", //
"test;X;test;Y;Method[foo].Argument[*]", //
"test;X;test;Y;Method[foo].Argument", //
"test;X;test;Y;Method[foo].Member", //
"TooManyColumns;;Member[Foo].Instance;too;many;columns", //
"TooFewColumns", //
"Foo;Foo;Method[foo].Arg[0]", //
"Foo;Foo;Method[foo].Argument[0-1]", //
"Foo;Foo;Method[foo].Argument[*]", //
"Foo;Foo;Method[foo].Argument", //
"Foo;Foo;Method[foo].Member", //
]
}
}
@@ -141,12 +139,12 @@ private class SinkFromModel extends ModelInput::SinkModelCsv {
override predicate row(string row) {
row =
[
"test;FooOrBar;Method[method].Argument[0];test-sink", //
";;Member[Foo].Method[sinkAnyArg].Argument[any];test-sink", //
";;Member[Foo].Method[sinkAnyNamedArg].Argument[any-named];test-sink", //
";;Member[Foo].Method[getSinks].ReturnValue.Element[any].Method[mySink].Argument[0];test-sink", //
";;Member[Foo].Method[arraySink].Argument[0].Element[any];test-sink", //
";;Member[Foo].Method[secondArrayElementIsSink].Argument[0].Element[1];test-sink", //
"~FooOrBar;Method[method].Argument[0];test-sink", //
"Foo!;Method[sinkAnyArg].Argument[any];test-sink", //
"Foo!;Method[sinkAnyNamedArg].Argument[any-named];test-sink", //
"Foo!;Method[getSinks].ReturnValue.Element[any].Method[mySink].Argument[0];test-sink", //
"Foo!;Method[arraySink].Argument[0].Element[any];test-sink", //
"Foo!;Method[secondArrayElementIsSink].Argument[0].Element[1];test-sink", //
]
}
}

View File

@@ -1,410 +0,0 @@
actionControllerControllerClasses
| action_controller/input_access.rb:1:1:50:3 | UsersController |
| action_controller/params_flow.rb:1:1:162:3 | MyController |
| action_controller/params_flow.rb:170:1:178:3 | Subclass |
| active_record/ActiveRecord.rb:23:1:39:3 | FooController |
| active_record/ActiveRecord.rb:41:1:64:3 | BarController |
| active_record/ActiveRecord.rb:66:1:98:3 | BazController |
| active_record/ActiveRecord.rb:100:1:108:3 | AnnotatedController |
| active_storage/active_storage.rb:39:1:45:3 | PostsController2 |
| app/controllers/comments_controller.rb:1:1:40:3 | CommentsController |
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController |
| app/controllers/photos_controller.rb:1:1:4:3 | PhotosController |
| app/controllers/posts_controller.rb:1:1:10:3 | PostsController |
| app/controllers/tags_controller.rb:1:1:2:3 | TagsController |
| app/controllers/users/notifications_controller.rb:2:3:5:5 | Users::NotificationsController |
actionControllerActionMethods
| action_controller/input_access.rb:2:3:49:5 | index |
| action_controller/params_flow.rb:2:3:4:5 | m1 |
| action_controller/params_flow.rb:6:3:8:5 | m2 |
| action_controller/params_flow.rb:10:3:12:5 | m2 |
| action_controller/params_flow.rb:14:3:16:5 | m3 |
| action_controller/params_flow.rb:18:3:20:5 | m4 |
| action_controller/params_flow.rb:22:3:24:5 | m5 |
| action_controller/params_flow.rb:26:3:28:5 | m6 |
| action_controller/params_flow.rb:30:3:32:5 | m7 |
| action_controller/params_flow.rb:34:3:36:5 | m8 |
| action_controller/params_flow.rb:38:3:40:5 | m9 |
| action_controller/params_flow.rb:42:3:44:5 | m10 |
| action_controller/params_flow.rb:46:3:48:5 | m11 |
| action_controller/params_flow.rb:50:3:52:5 | m12 |
| action_controller/params_flow.rb:54:3:56:5 | m13 |
| action_controller/params_flow.rb:58:3:60:5 | m14 |
| action_controller/params_flow.rb:62:3:64:5 | m15 |
| action_controller/params_flow.rb:66:3:68:5 | m16 |
| action_controller/params_flow.rb:70:3:72:5 | m17 |
| action_controller/params_flow.rb:74:3:76:5 | m18 |
| action_controller/params_flow.rb:78:3:80:5 | m19 |
| action_controller/params_flow.rb:82:3:84:5 | m20 |
| action_controller/params_flow.rb:86:3:88:5 | m21 |
| action_controller/params_flow.rb:90:3:92:5 | m22 |
| action_controller/params_flow.rb:94:3:96:5 | m23 |
| action_controller/params_flow.rb:98:3:100:5 | m24 |
| action_controller/params_flow.rb:102:3:104:5 | m25 |
| action_controller/params_flow.rb:106:3:108:5 | m26 |
| action_controller/params_flow.rb:110:3:113:5 | m27 |
| action_controller/params_flow.rb:115:3:118:5 | m28 |
| action_controller/params_flow.rb:120:3:123:5 | m29 |
| action_controller/params_flow.rb:125:3:132:5 | m30 |
| action_controller/params_flow.rb:134:3:141:5 | m31 |
| action_controller/params_flow.rb:143:3:150:5 | m32 |
| action_controller/params_flow.rb:152:3:159:5 | m33 |
| action_controller/params_flow.rb:165:3:167:5 | m34 |
| action_controller/params_flow.rb:171:3:173:5 | m35 |
| active_record/ActiveRecord.rb:27:3:38:5 | some_request_handler |
| active_record/ActiveRecord.rb:42:3:47:5 | some_other_request_handler |
| active_record/ActiveRecord.rb:49:3:63:5 | safe_paths |
| active_record/ActiveRecord.rb:67:3:69:5 | yet_another_handler |
| active_record/ActiveRecord.rb:71:3:73:5 | create1 |
| active_record/ActiveRecord.rb:75:3:77:5 | create2 |
| active_record/ActiveRecord.rb:79:3:81:5 | create3 |
| active_record/ActiveRecord.rb:83:3:85:5 | create4 |
| active_record/ActiveRecord.rb:87:3:89:5 | update1 |
| active_record/ActiveRecord.rb:91:3:93:5 | update2 |
| active_record/ActiveRecord.rb:95:3:97:5 | update3 |
| active_record/ActiveRecord.rb:101:3:103:5 | index |
| active_record/ActiveRecord.rb:105:3:107:5 | unsafe_action |
| active_storage/active_storage.rb:40:3:44:5 | create |
| app/controllers/comments_controller.rb:2:3:36:5 | index |
| app/controllers/comments_controller.rb:38:3:39:5 | show |
| app/controllers/foo/bars_controller.rb:5:3:7:5 | index |
| app/controllers/foo/bars_controller.rb:9:3:18:5 | show_debug |
| app/controllers/foo/bars_controller.rb:20:3:24:5 | show |
| app/controllers/foo/bars_controller.rb:26:3:28:5 | go_back |
| app/controllers/foo/bars_controller.rb:30:3:32:5 | go_back_2 |
| app/controllers/foo/bars_controller.rb:34:3:39:5 | show_2 |
| app/controllers/photos_controller.rb:2:3:3:5 | show |
| app/controllers/posts_controller.rb:2:3:3:5 | index |
| app/controllers/posts_controller.rb:5:3:6:5 | show |
| app/controllers/posts_controller.rb:8:3:9:5 | upvote |
| app/controllers/users/notifications_controller.rb:3:5:4:7 | mark_as_read |
paramsCalls
| action_controller/params_flow.rb:3:10:3:15 | call to params |
| action_controller/params_flow.rb:7:10:7:15 | call to params |
| action_controller/params_flow.rb:11:10:11:15 | call to params |
| action_controller/params_flow.rb:15:10:15:15 | call to params |
| action_controller/params_flow.rb:19:10:19:15 | call to params |
| action_controller/params_flow.rb:23:10:23:15 | call to params |
| action_controller/params_flow.rb:27:10:27:15 | call to params |
| action_controller/params_flow.rb:31:10:31:15 | call to params |
| action_controller/params_flow.rb:35:10:35:15 | call to params |
| action_controller/params_flow.rb:39:10:39:15 | call to params |
| action_controller/params_flow.rb:43:10:43:15 | call to params |
| action_controller/params_flow.rb:47:10:47:15 | call to params |
| action_controller/params_flow.rb:51:10:51:15 | call to params |
| action_controller/params_flow.rb:55:10:55:15 | call to params |
| action_controller/params_flow.rb:59:10:59:15 | call to params |
| action_controller/params_flow.rb:63:10:63:15 | call to params |
| action_controller/params_flow.rb:67:10:67:15 | call to params |
| action_controller/params_flow.rb:71:10:71:15 | call to params |
| action_controller/params_flow.rb:75:10:75:15 | call to params |
| action_controller/params_flow.rb:79:10:79:15 | call to params |
| action_controller/params_flow.rb:83:10:83:15 | call to params |
| action_controller/params_flow.rb:87:10:87:15 | call to params |
| action_controller/params_flow.rb:91:10:91:15 | call to params |
| action_controller/params_flow.rb:95:10:95:15 | call to params |
| action_controller/params_flow.rb:99:10:99:15 | call to params |
| action_controller/params_flow.rb:103:10:103:15 | call to params |
| action_controller/params_flow.rb:107:10:107:15 | call to params |
| action_controller/params_flow.rb:111:10:111:15 | call to params |
| action_controller/params_flow.rb:112:23:112:28 | call to params |
| action_controller/params_flow.rb:116:10:116:15 | call to params |
| action_controller/params_flow.rb:117:31:117:36 | call to params |
| action_controller/params_flow.rb:121:10:121:15 | call to params |
| action_controller/params_flow.rb:122:31:122:36 | call to params |
| action_controller/params_flow.rb:126:10:126:15 | call to params |
| action_controller/params_flow.rb:127:24:127:29 | call to params |
| action_controller/params_flow.rb:130:14:130:19 | call to params |
| action_controller/params_flow.rb:135:10:135:15 | call to params |
| action_controller/params_flow.rb:136:32:136:37 | call to params |
| action_controller/params_flow.rb:139:22:139:27 | call to params |
| action_controller/params_flow.rb:144:10:144:15 | call to params |
| action_controller/params_flow.rb:145:32:145:37 | call to params |
| action_controller/params_flow.rb:148:22:148:27 | call to params |
| action_controller/params_flow.rb:153:10:153:15 | call to params |
| action_controller/params_flow.rb:154:32:154:37 | call to params |
| action_controller/params_flow.rb:157:22:157:27 | call to params |
| action_controller/params_flow.rb:166:10:166:15 | call to params |
| action_controller/params_flow.rb:172:10:172:15 | call to params |
| action_controller/params_flow.rb:176:10:176:15 | call to params |
| action_mailer/mailer.rb:3:10:3:15 | call to params |
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
| active_record/ActiveRecord.rb:32:21:32:26 | call to params |
| active_record/ActiveRecord.rb:34:34:34:39 | call to params |
| active_record/ActiveRecord.rb:35:23:35:28 | call to params |
| active_record/ActiveRecord.rb:35:38:35:43 | call to params |
| active_record/ActiveRecord.rb:43:10:43:15 | call to params |
| active_record/ActiveRecord.rb:50:11:50:16 | call to params |
| active_record/ActiveRecord.rb:54:12:54:17 | call to params |
| active_record/ActiveRecord.rb:59:12:59:17 | call to params |
| active_record/ActiveRecord.rb:62:15:62:20 | call to params |
| active_record/ActiveRecord.rb:68:21:68:26 | call to params |
| active_record/ActiveRecord.rb:72:18:72:23 | call to params |
| active_record/ActiveRecord.rb:76:24:76:29 | call to params |
| active_record/ActiveRecord.rb:76:49:76:54 | call to params |
| active_record/ActiveRecord.rb:80:25:80:30 | call to params |
| active_record/ActiveRecord.rb:80:50:80:55 | call to params |
| active_record/ActiveRecord.rb:88:21:88:26 | call to params |
| active_record/ActiveRecord.rb:92:27:92:32 | call to params |
| active_record/ActiveRecord.rb:92:52:92:57 | call to params |
| active_record/ActiveRecord.rb:96:28:96:33 | call to params |
| active_record/ActiveRecord.rb:96:53:96:58 | call to params |
| active_record/ActiveRecord.rb:106:59:106:64 | call to params |
| active_storage/active_storage.rb:41:21:41:26 | call to params |
| active_storage/active_storage.rb:42:24:42:29 | call to params |
| 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 |
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params |
paramsSources
| action_controller/params_flow.rb:3:10:3:15 | call to params |
| action_controller/params_flow.rb:7:10:7:15 | call to params |
| action_controller/params_flow.rb:11:10:11:15 | call to params |
| action_controller/params_flow.rb:15:10:15:15 | call to params |
| action_controller/params_flow.rb:19:10:19:15 | call to params |
| action_controller/params_flow.rb:23:10:23:15 | call to params |
| action_controller/params_flow.rb:27:10:27:15 | call to params |
| action_controller/params_flow.rb:31:10:31:15 | call to params |
| action_controller/params_flow.rb:35:10:35:15 | call to params |
| action_controller/params_flow.rb:39:10:39:15 | call to params |
| action_controller/params_flow.rb:43:10:43:15 | call to params |
| action_controller/params_flow.rb:47:10:47:15 | call to params |
| action_controller/params_flow.rb:51:10:51:15 | call to params |
| action_controller/params_flow.rb:55:10:55:15 | call to params |
| action_controller/params_flow.rb:59:10:59:15 | call to params |
| action_controller/params_flow.rb:63:10:63:15 | call to params |
| action_controller/params_flow.rb:67:10:67:15 | call to params |
| action_controller/params_flow.rb:71:10:71:15 | call to params |
| action_controller/params_flow.rb:75:10:75:15 | call to params |
| action_controller/params_flow.rb:79:10:79:15 | call to params |
| action_controller/params_flow.rb:83:10:83:15 | call to params |
| action_controller/params_flow.rb:87:10:87:15 | call to params |
| action_controller/params_flow.rb:91:10:91:15 | call to params |
| action_controller/params_flow.rb:95:10:95:15 | call to params |
| action_controller/params_flow.rb:99:10:99:15 | call to params |
| action_controller/params_flow.rb:103:10:103:15 | call to params |
| action_controller/params_flow.rb:107:10:107:15 | call to params |
| action_controller/params_flow.rb:111:10:111:15 | call to params |
| action_controller/params_flow.rb:112:23:112:28 | call to params |
| action_controller/params_flow.rb:116:10:116:15 | call to params |
| action_controller/params_flow.rb:117:31:117:36 | call to params |
| action_controller/params_flow.rb:121:10:121:15 | call to params |
| action_controller/params_flow.rb:122:31:122:36 | call to params |
| action_controller/params_flow.rb:126:10:126:15 | call to params |
| action_controller/params_flow.rb:127:24:127:29 | call to params |
| action_controller/params_flow.rb:130:14:130:19 | call to params |
| action_controller/params_flow.rb:135:10:135:15 | call to params |
| action_controller/params_flow.rb:136:32:136:37 | call to params |
| action_controller/params_flow.rb:139:22:139:27 | call to params |
| action_controller/params_flow.rb:144:10:144:15 | call to params |
| action_controller/params_flow.rb:145:32:145:37 | call to params |
| action_controller/params_flow.rb:148:22:148:27 | call to params |
| action_controller/params_flow.rb:153:10:153:15 | call to params |
| action_controller/params_flow.rb:154:32:154:37 | call to params |
| action_controller/params_flow.rb:157:22:157:27 | call to params |
| action_controller/params_flow.rb:166:10:166:15 | call to params |
| action_controller/params_flow.rb:172:10:172:15 | call to params |
| action_controller/params_flow.rb:176:10:176:15 | call to params |
| action_mailer/mailer.rb:3:10:3:15 | call to params |
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
| active_record/ActiveRecord.rb:32:21:32:26 | call to params |
| active_record/ActiveRecord.rb:34:34:34:39 | call to params |
| active_record/ActiveRecord.rb:35:23:35:28 | call to params |
| active_record/ActiveRecord.rb:35:38:35:43 | call to params |
| active_record/ActiveRecord.rb:43:10:43:15 | call to params |
| active_record/ActiveRecord.rb:50:11:50:16 | call to params |
| active_record/ActiveRecord.rb:54:12:54:17 | call to params |
| active_record/ActiveRecord.rb:59:12:59:17 | call to params |
| active_record/ActiveRecord.rb:62:15:62:20 | call to params |
| active_record/ActiveRecord.rb:68:21:68:26 | call to params |
| active_record/ActiveRecord.rb:72:18:72:23 | call to params |
| active_record/ActiveRecord.rb:76:24:76:29 | call to params |
| active_record/ActiveRecord.rb:76:49:76:54 | call to params |
| active_record/ActiveRecord.rb:80:25:80:30 | call to params |
| active_record/ActiveRecord.rb:80:50:80:55 | call to params |
| active_record/ActiveRecord.rb:88:21:88:26 | call to params |
| active_record/ActiveRecord.rb:92:27:92:32 | call to params |
| active_record/ActiveRecord.rb:92:52:92:57 | call to params |
| active_record/ActiveRecord.rb:96:28:96:33 | call to params |
| active_record/ActiveRecord.rb:96:53:96:58 | call to params |
| active_record/ActiveRecord.rb:106:59:106:64 | call to params |
| active_storage/active_storage.rb:41:21:41:26 | call to params |
| active_storage/active_storage.rb:42:24:42:29 | call to params |
| 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 |
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params |
httpInputAccesses
| action_controller/input_access.rb:3:5:3:18 | call to params | ActionDispatch::Request#params |
| action_controller/input_access.rb:4:5:4:22 | call to parameters | ActionDispatch::Request#parameters |
| action_controller/input_access.rb:5:5:5:15 | call to GET | ActionDispatch::Request#GET |
| action_controller/input_access.rb:6:5:6:16 | call to POST | ActionDispatch::Request#POST |
| action_controller/input_access.rb:7:5:7:28 | call to query_parameters | ActionDispatch::Request#query_parameters |
| action_controller/input_access.rb:8:5:8:30 | call to request_parameters | ActionDispatch::Request#request_parameters |
| action_controller/input_access.rb:9:5:9:31 | call to filtered_parameters | ActionDispatch::Request#filtered_parameters |
| action_controller/input_access.rb:11:5:11:25 | call to authorization | ActionDispatch::Request#authorization |
| action_controller/input_access.rb:12:5:12:23 | call to script_name | ActionDispatch::Request#script_name |
| action_controller/input_access.rb:13:5:13:21 | call to path_info | ActionDispatch::Request#path_info |
| action_controller/input_access.rb:14:5:14:22 | call to user_agent | ActionDispatch::Request#user_agent |
| action_controller/input_access.rb:15:5:15:19 | call to referer | ActionDispatch::Request#referer |
| action_controller/input_access.rb:16:5:16:20 | call to referrer | ActionDispatch::Request#referrer |
| action_controller/input_access.rb:17:5:17:26 | call to host_authority | ActionDispatch::Request#host_authority |
| action_controller/input_access.rb:18:5:18:24 | call to content_type | ActionDispatch::Request#content_type |
| action_controller/input_access.rb:19:5:19:16 | call to host | ActionDispatch::Request#host |
| action_controller/input_access.rb:20:5:20:20 | call to hostname | ActionDispatch::Request#hostname |
| action_controller/input_access.rb:21:5:21:27 | call to accept_encoding | ActionDispatch::Request#accept_encoding |
| action_controller/input_access.rb:22:5:22:27 | call to accept_language | ActionDispatch::Request#accept_language |
| action_controller/input_access.rb:23:5:23:25 | call to if_none_match | ActionDispatch::Request#if_none_match |
| action_controller/input_access.rb:24:5:24:31 | call to if_none_match_etags | ActionDispatch::Request#if_none_match_etags |
| action_controller/input_access.rb:25:5:25:29 | call to content_mime_type | ActionDispatch::Request#content_mime_type |
| action_controller/input_access.rb:27:5:27:21 | call to authority | ActionDispatch::Request#authority |
| action_controller/input_access.rb:28:5:28:16 | call to host | ActionDispatch::Request#host |
| action_controller/input_access.rb:29:5:29:26 | call to host_authority | ActionDispatch::Request#host_authority |
| action_controller/input_access.rb:30:5:30:26 | call to host_with_port | ActionDispatch::Request#host_with_port |
| action_controller/input_access.rb:31:5:31:20 | call to hostname | ActionDispatch::Request#hostname |
| action_controller/input_access.rb:32:5:32:25 | call to forwarded_for | ActionDispatch::Request#forwarded_for |
| action_controller/input_access.rb:33:5:33:26 | call to forwarded_host | ActionDispatch::Request#forwarded_host |
| action_controller/input_access.rb:34:5:34:16 | call to port | ActionDispatch::Request#port |
| action_controller/input_access.rb:35:5:35:26 | call to forwarded_port | ActionDispatch::Request#forwarded_port |
| action_controller/input_access.rb:37:5:37:22 | call to media_type | ActionDispatch::Request#media_type |
| action_controller/input_access.rb:38:5:38:29 | call to media_type_params | ActionDispatch::Request#media_type_params |
| action_controller/input_access.rb:39:5:39:27 | call to content_charset | ActionDispatch::Request#content_charset |
| action_controller/input_access.rb:40:5:40:20 | call to base_url | ActionDispatch::Request#base_url |
| action_controller/input_access.rb:42:5:42:16 | call to body | ActionDispatch::Request#body |
| action_controller/input_access.rb:43:5:43:20 | call to raw_post | ActionDispatch::Request#raw_post |
| action_controller/input_access.rb:45:5:45:30 | ...[...] | ActionDispatch::Request#env[] |
| action_controller/input_access.rb:47:5:47:39 | ...[...] | ActionDispatch::Request#env[] |
| action_controller/params_flow.rb:3:10:3:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:7:10:7:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:11:10:11:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:15:10:15:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:19:10:19:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:23:10:23:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:27:10:27:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:31:10:31:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:35:10:35:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:39:10:39:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:43:10:43:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:47:10:47:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:51:10:51:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:55:10:55:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:59:10:59:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:63:10:63:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:67:10:67:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:71:10:71:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:75:10:75:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:79:10:79:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:83:10:83:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:87:10:87:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:91:10:91:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:95:10:95:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:99:10:99:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:103:10:103:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:107:10:107:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:111:10:111:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:112:23:112:28 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:116:10:116:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:117:31:117:36 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:121:10:121:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:122:31:122:36 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:126:10:126:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:127:24:127:29 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:130:14:130:19 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:135:10:135:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:136:32:136:37 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:139:22:139:27 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:144:10:144:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:145:32:145:37 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:148:22:148:27 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:153:10:153:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:154:32:154:37 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:157:22:157:27 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:166:10:166:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:172:10:172:15 | call to params | ActionController::Metal#params |
| action_controller/params_flow.rb:176:10:176:15 | call to params | ActionController::Metal#params |
| action_mailer/mailer.rb:3:10:3:15 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:28:30:28:35 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:29:29:29:34 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:30:31:30:36 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:32:21:32:26 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:34:34:34:39 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:35:23:35:28 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:35:38:35:43 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:43:10:43:15 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:50:11:50:16 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:54:12:54:17 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:59:12:59:17 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:62:15:62:20 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:68:21:68:26 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:72:18:72:23 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:76:24:76:29 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:76:49:76:54 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:80:25:80:30 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:80:50:80:55 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:88:21:88:26 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:92:27:92:32 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:92:52:92:57 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:96:28:96:33 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:96:53:96:58 | call to params | ActionController::Metal#params |
| active_record/ActiveRecord.rb:106:59:106:64 | call to params | ActionController::Metal#params |
| active_storage/active_storage.rb:41:21:41:26 | call to params | ActionController::Metal#params |
| active_storage/active_storage.rb:42:24:42:29 | call to params | ActionController::Metal#params |
| app/controllers/comments_controller.rb:3:5:3:18 | call to params | ActionDispatch::Request#params |
| app/controllers/comments_controller.rb:4:5:4:22 | call to parameters | ActionDispatch::Request#parameters |
| app/controllers/comments_controller.rb:5:5:5:15 | call to GET | ActionDispatch::Request#GET |
| app/controllers/comments_controller.rb:6:5:6:16 | call to POST | ActionDispatch::Request#POST |
| app/controllers/comments_controller.rb:7:5:7:28 | call to query_parameters | ActionDispatch::Request#query_parameters |
| app/controllers/comments_controller.rb:8:5:8:30 | call to request_parameters | ActionDispatch::Request#request_parameters |
| app/controllers/comments_controller.rb:9:5:9:31 | call to filtered_parameters | ActionDispatch::Request#filtered_parameters |
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies | ActionController::Metal#cookies |
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params | ActionController::Metal#params |
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params | ActionController::Metal#params |
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params | ActionController::Metal#params |
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params | ActionController::Metal#params |
| app/graphql/mutations/dummy.rb:5:24:5:25 | id | GraphQL RoutedParameter |
| app/graphql/mutations/dummy.rb:9:17:9:25 | something | GraphQL RoutedParameter |
| app/graphql/resolvers/dummy_resolver.rb:6:24:6:25 | id | GraphQL RoutedParameter |
| app/graphql/resolvers/dummy_resolver.rb:10:17:10:25 | something | GraphQL RoutedParameter |
| app/graphql/types/query_type.rb:10:18:10:23 | number | GraphQL RoutedParameter |
| app/graphql/types/query_type.rb:18:23:18:33 | blah_number | GraphQL RoutedParameter |
| app/graphql/types/query_type.rb:27:20:27:25 | **args | GraphQL RoutedParameter |
| app/graphql/types/query_type.rb:36:34:36:37 | arg1 | GraphQL RoutedParameter |
| app/graphql/types/query_type.rb:36:41:36:46 | **rest | GraphQL RoutedParameter |
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params | ActionController::Metal#params |
cookiesCalls
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies |
cookiesSources
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies |
redirectToCalls
| app/controllers/foo/bars_controller.rb:17:5:17:30 | call to redirect_to |
| app/controllers/foo/bars_controller.rb:27:5:27:39 | call to redirect_back_or_to |
| app/controllers/foo/bars_controller.rb:31:5:31:56 | call to redirect_back |
actionControllerHelperMethods
getAssociatedControllerClasses
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
controllerTemplateFiles
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
| app/controllers/foo/bars_controller.rb:3:1:46:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
headerWriteAccesses
| app/controllers/comments_controller.rb:15:5:15:35 | call to []= | content-type | app/controllers/comments_controller.rb:15:39:15:49 | ... = ... |
| app/controllers/comments_controller.rb:16:5:16:46 | call to set_header | content-length | app/controllers/comments_controller.rb:16:43:16:45 | 100 |
| app/controllers/comments_controller.rb:17:5:17:39 | call to []= | x-custom-header | app/controllers/comments_controller.rb:17:43:17:46 | ... = ... |
| app/controllers/comments_controller.rb:18:5:18:39 | call to []= | x-another-custom-header | app/controllers/comments_controller.rb:18:43:18:47 | ... = ... |
| app/controllers/comments_controller.rb:19:5:19:49 | call to add_header | x-yet-another | app/controllers/comments_controller.rb:19:42:19:49 | "indeed" |
| app/controllers/comments_controller.rb:25:5:25:21 | call to location= | location | app/controllers/comments_controller.rb:25:25:25:36 | ... = ... |
| app/controllers/comments_controller.rb:26:5:26:26 | call to cache_control= | cache-control | app/controllers/comments_controller.rb:26:30:26:36 | ... = ... |
| app/controllers/comments_controller.rb:27:5:27:27 | call to _cache_control= | cache-control | app/controllers/comments_controller.rb:27:31:27:37 | ... = ... |
| app/controllers/comments_controller.rb:28:5:28:17 | call to etag= | etag | app/controllers/comments_controller.rb:28:21:28:27 | ... = ... |
| app/controllers/comments_controller.rb:29:5:29:20 | call to charset= | content-type | app/controllers/comments_controller.rb:29:24:29:30 | ... = ... |
| app/controllers/comments_controller.rb:30:5:30:25 | call to content_type= | content-type | app/controllers/comments_controller.rb:30:29:30:35 | ... = ... |
| app/controllers/comments_controller.rb:32:5:32:17 | call to date= | date | app/controllers/comments_controller.rb:32:21:32:30 | ... = ... |
| app/controllers/comments_controller.rb:33:5:33:26 | call to last_modified= | last-modified | app/controllers/comments_controller.rb:33:30:33:43 | ... = ... |
| app/controllers/comments_controller.rb:34:5:34:22 | call to weak_etag= | etag | app/controllers/comments_controller.rb:34:26:34:32 | ... = ... |
| app/controllers/comments_controller.rb:35:5:35:24 | call to strong_etag= | etag | app/controllers/comments_controller.rb:35:28:35:34 | ... = ... |

View File

@@ -34,15 +34,26 @@ actionDispatchRoutes
| app/config/routes.rb:49:5:49:95 | call to delete | delete | users/:user/notifications | users/notifications | destroy |
| app/config/routes.rb:50:5:50:94 | call to post | post | users/:user/notifications/:notification_id/mark_as_read | users/notifications | mark_as_read |
actionDispatchControllerMethods
| app/config/routes.rb:2:3:8:5 | call to resources | action_controller/controllers/posts_controller.rb:2:3:3:5 | index |
| app/config/routes.rb:2:3:8:5 | call to resources | action_controller/controllers/posts_controller.rb:5:3:6:5 | show |
| 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 | action_controller/controllers/comments_controller.rb:2:3:36:5 | index |
| app/config/routes.rb:3:5:6:7 | call to resources | action_controller/controllers/comments_controller.rb:38:3:44:5 | show |
| app/config/routes.rb:3:5:6:7 | call to resources | action_controller/controllers/comments_controller.rb:50:3:52:5 | destroy |
| 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:7:5:7:37 | call to post | action_controller/controllers/posts_controller.rb:8:3:9:5 | upvote |
| 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 | action_controller/controllers/photos_controller.rb:2:3:3:5 | show |
| 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 | action_controller/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 |
| app/config/routes.rb:29:3:29:69 | call to match | action_controller/controllers/photos_controller.rb:2:3:3:5 | show |
| app/config/routes.rb:29:3:29:69 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
| app/config/routes.rb:30:3:30:50 | call to match | action_controller/controllers/photos_controller.rb:2:3:3:5 | show |
| app/config/routes.rb:30:3:30:50 | call to match | app/controllers/photos_controller.rb:2:3:3:5 | show |
| app/config/routes.rb:50:5:50:94 | call to post | action_controller/controllers/users/notifications_controller.rb:3:5:4:7 | mark_as_read |
| app/config/routes.rb:50:5:50:94 | call to post | app/controllers/users/notifications_controller.rb:3:5:4:7 | mark_as_read |
underscore
| Foo | foo |

View File

@@ -29,9 +29,7 @@ query predicate underscore(string input, string output) {
]
}
query predicate mimeTypeInstances(API::Node n) {
n = ModelOutput::getATypeNode("actiondispatch", "Mime::Type")
}
query predicate mimeTypeInstances(API::Node n) { n = ModelOutput::getATypeNode("Mime::Type") }
query predicate mimeTypeMatchRegExpInterpretations(
ActionDispatch::MimeTypeMatchRegExpInterpretation s

Some files were not shown because too many files have changed in this diff Show More