Ruby: Data-flow through hashes

This commit is contained in:
Tom Hvitved
2022-04-20 13:37:22 +02:00
parent 6781a76b96
commit faf24a4f18
16 changed files with 2992 additions and 202 deletions

View File

@@ -57,6 +57,24 @@ module SummaryComponent {
*/
SummaryComponent elementAny() { result = SC::content(TAnyElementContent()) }
/**
* Gets a summary component that represents an element in a collection at known
* integer index `lower` or above.
*/
SummaryComponent elementLowerBound(int lower) {
result = SC::content(TElementLowerBoundContent(lower))
}
/** Gets a summary component that represents a value in a pair at an unknown key. */
SummaryComponent pairValueUnknown() {
result = SC::content(TSingletonContent(TUnknownPairValueContent()))
}
/** Gets a summary component that represents a value in a pair at a known key. */
SummaryComponent pairValueKnown(ConstantValue cv) {
result = SC::content(TSingletonContent(TKnownPairValueContent(cv)))
}
/** Gets a summary component that represents the return value of a call. */
SummaryComponent return() { result = SC::return(any(NormalReturnKind rk)) }
}

View File

@@ -6,6 +6,7 @@ private import DataFlowPublic
private import DataFlowDispatch
private import SsaImpl as SsaImpl
private import FlowSummaryImpl as FlowSummaryImpl
private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
/** Gets the callable in which this node occurs. */
DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() }
@@ -177,7 +178,7 @@ private class Argument extends CfgNodes::ExprCfgNode {
exists(int i |
this = call.getArgument(i) and
not this.getExpr() instanceof BlockArgument and
not exists(this.getExpr().(Pair).getKey().getConstantValue().getSymbol()) and
not this.getExpr().(Pair).getKey().getConstantValue().isSymbol(_) and
arg.isPositional(i)
)
or
@@ -334,7 +335,10 @@ private module Cached {
cached
newtype TContentSet =
TSingletonContent(Content c) or
TAnyElementContent()
TAnyElementContent() or
TElementLowerBoundContent(int lower) {
FlowSummaryImplSpecific::ParsePositions::isParsedElementLowerBoundPosition(_, lower)
}
cached
newtype TContent =
@@ -342,8 +346,10 @@ private module Cached {
not cv.isInt(_) or
cv.getInt() in [0 .. 10]
} or
TFieldContent(string name) { name = any(InstanceVariable v).getName() } or
TUnknownElementContent()
TUnknownElementContent() or
TKnownPairValueContent(ConstantValue cv) or
TUnknownPairValueContent() or
TFieldContent(string name) { name = any(InstanceVariable v).getName() }
/**
* Holds if `e` is an `ExprNode` that may be returned by a call to `c`.
@@ -362,6 +368,8 @@ private module Cached {
class TElementContent = TKnownElementContent or TUnknownElementContent;
class TPairValueContent = TKnownPairValueContent or TUnknownPairValueContent;
import Cached
/** Holds if `n` should be hidden from path explanations. */
@@ -818,6 +826,25 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
).getReceiver()
or
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2)
or
// Needed for pairs passed into method calls where the key is not a symbol,
// that is, where it is not a keyword argument.
node2.asExpr() =
any(CfgNodes::ExprNodes::PairCfgNode pair |
exists(CfgNodes::ExprCfgNode key |
key = pair.getKey() and
pair.getValue() = node1.asExpr()
|
exists(ConstantValue cv |
cv = key.getConstantValue() and
not cv.isSymbol(_) and // handled as a keyword argument
c.isSingleton(TKnownPairValueContent(cv))
)
or
not exists(key.getConstantValue()) and
c.isSingleton(TUnknownPairValueContent())
)
)
}
/**

View File

@@ -242,6 +242,35 @@ module Content {
not exists(TKnownElementContent(cv)) and
result = TUnknownElementContent()
}
/**
* Gets the constant value of `e`, which corresponds to a valid known
* element index. Unlike calling simply `e.getConstantValue()`, this
* excludes negative array indices.
*/
ConstantValue getKnownElementIndex(Expr e) {
result = getElementContent(e.getConstantValue()).(KnownElementContent).getIndex()
}
/** A value in a pair with a known or unknown key. */
class PairValueContent extends Content, TPairValueContent { }
/** A value in a pair with a known key. */
class KnownPairValueContent extends PairValueContent, TKnownPairValueContent {
private ConstantValue cv;
KnownPairValueContent() { this = TKnownPairValueContent(cv) }
/** Gets the index in the collection. */
ConstantValue getIndex() { result = cv }
override string toString() { result = "pair " + cv }
}
/** A value in a pair with an unknown key. */
class UnknownPairValueContent extends PairValueContent, TUnknownPairValueContent {
override string toString() { result = "pair" }
}
}
/**
@@ -257,6 +286,12 @@ class ContentSet extends TContentSet {
/** Holds if this content set represents all `ElementContent`s. */
predicate isAnyElement() { this = TAnyElementContent() }
/**
* Holds if this content set represents all `KnownElementContent`s where
* the index is an integer greater than or equal to `lower`.
*/
predicate isElementLowerBound(int lower) { this = TElementLowerBoundContent(lower) }
/** Gets a textual representation of this content set. */
string toString() {
exists(Content c |
@@ -265,7 +300,12 @@ class ContentSet extends TContentSet {
)
or
this.isAnyElement() and
result = "any array element"
result = "any element"
or
exists(int lower |
this.isElementLowerBound(lower) and
result = lower + ".."
)
}
/** Gets a content that may be stored into when storing into this set. */
@@ -274,6 +314,9 @@ class ContentSet extends TContentSet {
or
this.isAnyElement() and
result = TUnknownElementContent()
or
this.isElementLowerBound(_) and
result = TUnknownElementContent()
}
/** Gets a content that may be read from when reading from this set. */
@@ -282,6 +325,12 @@ class ContentSet extends TContentSet {
or
this.isAnyElement() and
result instanceof Content::ElementContent
or
exists(int lower, int i |
this.isElementLowerBound(lower) and
result.(Content::KnownElementContent).getIndex().isInt(i) and
i >= lower
)
}
}

View File

@@ -67,6 +67,11 @@ private SummaryComponent interpretElementArg(string arg) {
arg = "any" and
result = FlowSummary::SummaryComponent::elementAny()
or
exists(int lower |
ParsePositions::isParsedElementLowerBoundPosition(arg, lower) and
result = FlowSummary::SummaryComponent::elementLowerBound(lower)
)
or
exists(ConstantValue cv | result = FlowSummary::SummaryComponent::elementKnown(cv) |
cv.isInt(AccessPath::parseInt(arg))
or
@@ -103,6 +108,16 @@ SummaryComponent interpretComponentSpecific(AccessPathToken c) {
interpretElementArg(c.getAnArgument("WithoutElement")) and
result = FlowSummary::SummaryComponent::withoutContent(cs)
)
or
exists(string arg | arg = c.getAnArgument("PairValue") |
arg = "?" and
result = FlowSummary::SummaryComponent::pairValueUnknown()
or
exists(ConstantValue cv |
result = FlowSummary::SummaryComponent::pairValueKnown(cv) and
cv.serialize() = arg
)
)
}
/** Gets the textual representation of a summary component in the format used for flow summaries. */
@@ -217,6 +232,13 @@ module ParsePositions {
)
}
private predicate isElementBody(string body) {
exists(AccessPathToken tok |
tok.getName() = "Element" and
body = tok.getAnArgument()
)
}
predicate isParsedParameterPosition(string c, int i) {
isParamBody(c) and
i = AccessPath::parseInt(c)
@@ -241,6 +263,11 @@ module ParsePositions {
isArgBody(c) and
c = paramName + ":"
}
predicate isParsedElementLowerBoundPosition(string c, int lower) {
isElementBody(c) and
lower = AccessPath::parseLowerBound(c)
}
}
/** Gets the argument position obtained by parsing `X` in `Parameter[X]`. */

View File

@@ -10,6 +10,7 @@ import core.Object::Object
import core.Kernel::Kernel
import core.Module
import core.Array
import core.Hash
import core.String
import core.Regexp
import core.IO

View File

@@ -6,10 +6,17 @@ private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.internal.DataFlowDispatch
/** An array index that may be tracked precisely in data flow. */
private class ArrayIndex extends int {
ArrayIndex() { this = any(DataFlow::Content::KnownElementContent c).getIndex().getInt() }
}
private string lastBlockParam(MethodCall mc, string name, int lastBlockParam) {
mc.getMethodName() = name and
result = name + "(" + lastBlockParam + ")" and
lastBlockParam = mc.getBlock().getNumberOfParameters() - 1
}
/**
* Provides flow summaries for the `Array` class.
*
@@ -19,23 +26,9 @@ private class ArrayIndex extends int {
* module instead.
*/
module Array {
/**
* Gets the constant value of `arg`, which corresponds to a valid known
* element index. Unlike calling simply `arg.getConstantValue()`, this
* excludes negative array indices.
*/
bindingset[arg]
private ConstantValue getKnownElementIndex(Expr arg) {
result =
DataFlow::Content::getElementContent(arg.getConstantValue())
.(DataFlow::Content::KnownElementContent)
.getIndex()
}
bindingset[arg]
private predicate isUnknownElementIndex(Expr arg) {
not exists(getKnownElementIndex(arg)) and
not arg instanceof RangeLiteral
private predicate isUnknownElementIndex(Expr e) {
not exists(DataFlow::Content::getKnownElementIndex(e)) and
not e instanceof RangeLiteral
}
private class ArrayLiteralSummary extends SummarizedCallable {
@@ -195,11 +188,12 @@ module Array {
ElementReferenceReadKnownSummary() {
this = methodName + "(" + cv.serialize() + ")" and
mc.getNumberOfArguments() = 1 and
cv = getKnownElementIndex(mc.getArgument(0))
cv = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)) and
if methodName = "slice" then cv.isInt(_) else any()
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[" + [cv.serialize(), "?"] + "]" and
input = "Argument[self].Element[?," + cv.serialize() + "]" and
output = "ReturnValue" and
preservesValue = true
}
@@ -230,7 +224,7 @@ module Array {
ElementReferenceRangeReadKnownSummary() {
mc.getNumberOfArguments() = 2 and
start = getKnownElementIndex(mc.getArgument(0)).getInt() and
start = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).getInt() and
exists(int length | mc.getArgument(1).getConstantValue().isInt(length) |
end = (start + length - 1) and
this = "[](" + start + ", " + length + ")"
@@ -260,8 +254,8 @@ module Array {
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
(
input = "Argument[self].Element[?]" and
output = "ReturnValue.Element[?]"
input = "Argument[self].WithElement[?]" and
output = "ReturnValue"
or
exists(ArrayIndex i | i >= start and i <= end |
input = "Argument[self].Element[" + i + "]" and
@@ -281,8 +275,8 @@ module Array {
(
mc.getNumberOfArguments() = 2 and
(
not mc.getArgument(0).getConstantValue().isInt(_) or
not mc.getArgument(1).getConstantValue().isInt(_)
not exists(mc.getArgument(0).getConstantValue()) or
not exists(mc.getArgument(1).getConstantValue())
)
or
mc.getNumberOfArguments() = 1 and
@@ -296,7 +290,7 @@ module Array {
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
input = "Argument[self].Element[?,0..]" and
output = "ReturnValue.Element[?]" and
preservesValue = true
}
@@ -318,7 +312,7 @@ module Array {
ElementReferenceStoreKnownSummary() {
mc.getNumberOfArguments() = 2 and
cv = getKnownElementIndex(mc.getArgument(0)) and
cv = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)) and
this = "[" + cv.serialize() + "]="
}
@@ -382,8 +376,8 @@ module Array {
AssocSummary() { this = ["assoc", "rassoc"] }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any].Element[any]" and
output = "ReturnValue.Element[?]" and
input = "Argument[self].Element[any].WithElement[any]" and
output = "ReturnValue" and
preservesValue = true
}
}
@@ -403,11 +397,11 @@ module Array {
AtKnownSummary() {
this = "at(" + cv.serialize() + "]" and
mc.getNumberOfArguments() = 1 and
cv = getKnownElementIndex(mc.getArgument(0))
cv = DataFlow::Content::getKnownElementIndex(mc.getArgument(0))
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[" + [cv.serialize(), "?"] + "]" and
input = "Argument[self].Element[" + cv.serialize() + ",?]" and
output = "ReturnValue" and
preservesValue = true
}
@@ -490,7 +484,7 @@ module Array {
CompactBangSummary() { this = "compact!" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
input = "Argument[self].Element[?,0..]" and
output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"] and
preservesValue = true
}
@@ -519,19 +513,82 @@ module Array {
}
}
private class DeleteSummary extends SimpleSummarizedCallable {
DeleteSummary() { this = "delete" }
abstract private class DeleteSummary extends SummarizedCallable {
MethodCall mc;
bindingset[this]
DeleteSummary() { mc.getMethodName() = "delete" }
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[self].Element[any]" and
output = ["Argument[self].Element[?]", "ReturnValue"]
input = "Argument[self].WithoutElement[any]" and
output = "Argument[self]"
or
input = "Argument[self].WithElement[?]" and
output = "Argument[self]"
or
input = "Argument[block].ReturnValue" and
output = "ReturnValue"
) and
preservesValue = true
}
}
private class DeleteKnownSummary extends DeleteSummary {
private ConstantValue cv;
DeleteKnownSummary() {
this = "delete(" + cv.serialize() + ")" and
mc.getArgument(0).getConstantValue() = cv
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
super.propagatesFlowExt(input, output, preservesValue)
or
(
(
if cv.isInt(_)
then
// array indices may get shifted
input = "Argument[self].WithoutElement[" + cv.serialize() + "].Element[0..]" and
output = "Argument[self].Element[?]"
or
input = "Argument[self].WithoutElement[0..]" and
output = "Argument[self]"
else (
input = "Argument[self].WithoutElement[" + cv.serialize() + "]" and
output = "Argument[self]"
)
)
or
input = "Argument[self].WithoutElement[any]" and
input = "Argument[self].Element[" + cv.serialize() + ",?]" and
output = "ReturnValue"
) and
preservesValue = true
}
}
private class DeleteUnknownSummary extends DeleteSummary {
DeleteUnknownSummary() {
this = "delete" and
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0)))
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
super.propagatesFlowExt(input, output, preservesValue)
or
(
// array indices may get shifted
input = "Argument[self].Element[0..]" and
output = "Argument[self].Element[?]"
or
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
output = "Argument[self]"
or
input = "Argument[self].Element[any]" and
output = "ReturnValue"
) and
preservesValue = true
}
@@ -603,17 +660,26 @@ module Array {
}
}
private class DeleteIfSummary extends SimpleSummarizedCallable {
DeleteIfSummary() { this = "delete_if" }
private class DeleteIfSummary extends SummarizedCallable {
MethodCall mc;
int lastBlockParam;
DeleteIfSummary() { this = lastBlockParam(mc, "delete_if", lastBlockParam) }
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output =
["Argument[block].Parameter[0]", "ReturnValue.Element[?]", "Argument[self].Element[?]"] and
preservesValue = true
or
input = "Argument[self].WithoutElement[any]" and
output = "Argument[self]" and
(
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[" + lastBlockParam + "]"
or
// array indices may get shifted
input = "Argument[self].Element[0..]" and
output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"]
or
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
output = ["ReturnValue", "Argument[self]"]
) and
preservesValue = true
}
}
@@ -631,9 +697,9 @@ module Array {
private string getDigArg(MethodCall dig, int i) {
dig.getMethodName() = "dig" and
exists(Expr arg | arg = dig.getArgument(i) |
result = getKnownElementIndex(arg).(ConstantValue::ConstantIntegerValue).serialize()
result = DataFlow::Content::getKnownElementIndex(arg).serialize()
or
not getKnownElementIndex(arg).isInt(_) and
not exists(DataFlow::Content::getKnownElementIndex(arg)) and
result = "?"
)
}
@@ -647,7 +713,7 @@ module Array {
private string buildDigInputSpecComponent(RelevantDigMethodCall dig, int i) {
exists(string s |
s = getDigArg(dig, i) and
if s = "?" then result = "any" else result = [s, "?"]
if s = "?" then result = "any" else result = s + ",?"
)
}
@@ -683,14 +749,24 @@ module Array {
}
}
private class EachSummary extends SimpleSummarizedCallable {
// `each` and `reverse_each` are the same in terms of flow inputs/outputs.
EachSummary() { this = ["each", "reverse_each"] }
private class EachSummary extends SummarizedCallable {
MethodCall mc;
int lastBlockParam;
EachSummary() {
exists(string name |
// `each` and `reverse_each` are the same in terms of flow inputs/outputs.
name = ["each", "reverse_each"] and
this = lastBlockParam(mc, name, lastBlockParam)
)
}
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[0]"
output = "Argument[block].Parameter[" + lastBlockParam + "]"
or
input = "Argument[self].WithElement[any]" and
output = "ReturnValue"
@@ -700,7 +776,7 @@ module Array {
}
private class EachIndexSummary extends SimpleSummarizedCallable {
EachIndexSummary() { this = "each_index" }
EachIndexSummary() { this = ["each_index", "each_key"] }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].WithElement[any]" and
@@ -719,17 +795,17 @@ module Array {
}
private class FetchKnownSummary extends FetchSummary {
int i;
ConstantValue cv;
FetchKnownSummary() {
this = "fetch(" + i + ")" and
mc.getArgument(0).getConstantValue().isInt(i) and
i >= 0
this = "fetch(" + cv.serialize() + ")" and
cv = mc.getArgument(0).getConstantValue() and
not cv.isInt(any(int i | i < 0))
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[self].Element[?," + i + "]" and
input = "Argument[self].Element[?," + cv.serialize() + "]" and
output = "ReturnValue"
or
input = "Argument[0]" and
@@ -745,7 +821,9 @@ module Array {
private class FetchUnknownSummary extends FetchSummary {
FetchUnknownSummary() {
this = "fetch(index)" and
not exists(int i | mc.getArgument(0).getConstantValue().isInt(i) and i >= 0)
not exists(ConstantValue cv |
cv = mc.getArgument(0).getConstantValue() and not cv.isInt(any(int i | i < 0))
)
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
@@ -936,17 +1014,29 @@ module Array {
override MethodCall getACall() { result = mc }
}
private class KeepIfSummary extends SimpleSummarizedCallable {
KeepIfSummary() { this = "keep_if" }
private class KeepIfSummary extends SummarizedCallable {
MethodCall mc;
int lastBlockParam;
KeepIfSummary() { this = lastBlockParam(mc, "keep_if", lastBlockParam) }
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output =
["ReturnValue.Element[?]", "Argument[self].Element[?]", "Argument[block].Parameter[0]"] and
preservesValue = true
or
input = "Argument[self].WithoutElement[any]" and
output = "Argument[self]" and
(
input = "Argument[self].WithoutElement[any]" and
output = "Argument[self]"
or
// array indices may get shifted
input = "Argument[self].Element[0..]" and
output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"]
or
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
output = ["ReturnValue", "Argument[self]"]
or
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[" + lastBlockParam + "]"
) and
preservesValue = true
}
}
@@ -1107,17 +1197,26 @@ module Array {
}
}
private class RejectBangSummary extends SimpleSummarizedCallable {
RejectBangSummary() { this = "reject!" }
private class RejectBangSummary extends SummarizedCallable {
MethodCall mc;
int lastBlockParam;
RejectBangSummary() { this = lastBlockParam(mc, "reject!", lastBlockParam) }
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output =
["ReturnValue.Element[?]", "Argument[self].Element[?]", "Argument[block].Parameter[0]"] and
preservesValue = true
or
input = "Argument[self].WithoutElement[any]" and
output = "Argument[self]" and
(
// array indices may get shifted
input = "Argument[self].Element[0..]" and
output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"]
or
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
output = ["ReturnValue", "Argument[self]"]
or
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[" + lastBlockParam + "]"
) and
preservesValue = true
}
}
@@ -1169,7 +1268,7 @@ module Array {
private int c;
RotateKnownSummary() {
getKnownElementIndex(mc.getArgument(0)).isInt(c) and
DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).isInt(c) and
this = "rotate(" + c + ")"
or
not exists(mc.getArgument(0)) and c = 1 and this = "rotate"
@@ -1197,7 +1296,7 @@ module Array {
RotateUnknownSummary() {
this = "rotate(index)" and
exists(mc.getArgument(0)) and
not getKnownElementIndex(mc.getArgument(0)).isInt(_)
not DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).isInt(_)
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
@@ -1267,18 +1366,34 @@ module Array {
}
}
private class SelectBangSummary extends SimpleSummarizedCallable {
// `filter!` is an alias for `select!`
SelectBangSummary() { this = ["select!", "filter!"] }
private class SelectBangSummary extends SummarizedCallable {
MethodCall mc;
int lastBlockParam;
SelectBangSummary() {
exists(string name |
name = ["select!", "filter!"] and
this = lastBlockParam(mc, name, lastBlockParam)
)
}
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output =
["Argument[block].Parameter[0]", "Argument[self].Element[?]", "ReturnValue.Element[?]"] and
preservesValue = true
or
input = "Argument[self].WithoutElement[any]" and
output = "Argument[self]" and
(
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[" + lastBlockParam + "]"
or
// array indices may get shifted
input = "Argument[self].Element[0..]" and
output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"]
or
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
output = ["ReturnValue", "Argument[self]"]
or
input = "Argument[self].WithoutElement[any]" and
output = "Argument[self]"
) and
preservesValue = true
}
}
@@ -1306,8 +1421,18 @@ module Array {
or
preservesValue = true and
(
input = "Argument[self].WithoutElement[0..]" and
output = "Argument[self]"
or
input = "Argument[self].Element[?]" and
output = ["ReturnValue", "Argument[self].Element[?]"]
output =
[
"ReturnValue", // array
"ReturnValue.Element[1]" // hash
]
or
input = "Argument[self].WithoutElement[0..].WithoutElement[?].Element[any]" and
output = "ReturnValue.Element[1]"
or
exists(ArrayIndex i | input = "Argument[self].Element[" + i + "]" |
i = 0 and output = "ReturnValue"
@@ -1403,7 +1528,7 @@ module Array {
SliceBangKnownIndexSummary() {
this = "slice!(" + n + ")" and
mc.getNumberOfArguments() = 1 and
n = getKnownElementIndex(mc.getArgument(0)).getInt()
n = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).getInt()
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
@@ -1461,7 +1586,7 @@ module Array {
SliceBangRangeKnownSummary() {
mc.getNumberOfArguments() = 2 and
start = getKnownElementIndex(mc.getArgument(0)).getInt() and
start = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).getInt() and
exists(int length | mc.getArgument(1).getConstantValue().isInt(length) |
end = (start + length - 1) and
this = "slice!(" + start + ", " + length + ")"
@@ -1633,47 +1758,43 @@ module Array {
override Call getACall() { result = mc }
}
/**
* A call to `values_at` where all the arguments are known, positive integers.
*/
private string getValuesAtComponent(MethodCall mc, int i) {
mc.getMethodName() = "values_at" and
result = DataFlow::Content::getKnownElementIndex(mc.getArgument(i)).serialize()
}
private class ValuesAtKnownSummary extends ValuesAtSummary {
ValuesAtKnownSummary() {
this = "values_at(known)" and
forall(int i | i in [0 .. mc.getNumberOfArguments() - 1] |
getKnownElementIndex(mc.getArgument(i)).isInt(_)
)
this =
"values_at(" +
strictconcat(int i |
exists(mc.getArgument(i))
|
getValuesAtComponent(mc, i), "," order by i
) + ")"
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
(
input = "Argument[self].Element[?]" and
output = "ReturnValue.Element[?]"
or
exists(ArrayIndex elementIndex, int argIndex |
argIndex in [0 .. mc.getNumberOfArguments() - 1] and
getKnownElementIndex(mc.getArgument(argIndex)).isInt(elementIndex)
|
input = "Argument[self].Element[" + elementIndex + "]" and
output = "ReturnValue.Element[" + argIndex + "]"
)
)
super.propagatesFlowExt(input, output, preservesValue)
or
exists(string s, int i |
s = getValuesAtComponent(mc, i) and
input = "Argument[self].Element[" + s + "]" and
output = "ReturnValue.Element[" + i + "]"
) and
preservesValue = true
}
}
/**
* A call to `values_at` where at least one of the arguments is not a known,
* positive integer.
*/
private class ValuesAtUnknownSummary extends ValuesAtSummary {
ValuesAtUnknownSummary() {
this = "values_at(unknown)" and
exists(int i | i in [0 .. mc.getNumberOfArguments() - 1] |
not getKnownElementIndex(mc.getArgument(i)).isInt(_)
)
exists(int i | exists(mc.getArgument(i)) | not exists(getValuesAtComponent(mc, i))) and
this = "values_at(?)"
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
super.propagatesFlowExt(input, output, preservesValue)
or
input = "Argument[self].Element[any]" and
output = "ReturnValue.Element[?]" and
preservesValue = true
@@ -1742,9 +1863,16 @@ module Enumerable {
CompactSummary() { this = "compact" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
input = "Argument[self].Element[?,0..]" and
output = "ReturnValue.Element[?]" and
preservesValue = true
or
exists(ConstantValue cv |
not cv.isInt(_) and
input = "Argument[self].WithElement[" + cv.serialize() + "]" and
output = "ReturnValue" and
preservesValue = true
)
}
}
@@ -2254,33 +2382,75 @@ module Enumerable {
}
}
private class QuerySummary extends SimpleSummarizedCallable {
QuerySummary() { this = ["all?", "any?", "none?", "one?"] }
private class QuerySummary extends SummarizedCallable {
MethodCall mc;
int lastBlockParam;
QuerySummary() {
exists(string name |
name = ["all?", "any?", "none?", "one?"] and
this = lastBlockParam(mc, name, lastBlockParam)
)
}
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[0]" and
output = "Argument[block].Parameter[" + lastBlockParam + "]" and
preservesValue = true
}
}
private class RejectSummary extends SimpleSummarizedCallable {
RejectSummary() { this = "reject" }
private class RejectSummary extends SummarizedCallable {
MethodCall mc;
int lastBlockParam;
RejectSummary() { this = lastBlockParam(mc, "reject", lastBlockParam) }
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and
(
// array indices may get shifted
input = "Argument[self].Element[0..]" and
output = "ReturnValue.Element[?]"
or
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
output = "ReturnValue"
or
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[" + lastBlockParam + "]"
) and
preservesValue = true
}
}
private class SelectSummary extends SimpleSummarizedCallable {
// `find_all` and `filter` are aliases of `select`.
SelectSummary() { this = ["select", "find_all", "filter"] }
private class SelectSummary extends SummarizedCallable {
MethodCall mc;
int lastBlockParam;
SelectSummary() {
exists(string name |
name = ["select", "find_all", "filter"] and
this = lastBlockParam(mc, name, lastBlockParam)
)
}
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and
(
// array indices may get shifted
input = "Argument[self].Element[0..]" and
output = "ReturnValue.Element[?]"
or
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
output = "ReturnValue"
or
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[" + lastBlockParam + "]"
) and
preservesValue = true
}
}
@@ -2406,7 +2576,7 @@ module Enumerable {
ToASummary() { this = ["to_a", "entries", "to_ary"] }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self]" and
input = "Argument[self].WithElement[?,0..]" and
output = "ReturnValue" and
preservesValue = true
}

View File

@@ -0,0 +1,533 @@
/** Provides flow summaries for the `Hash` class. */
private import codeql.ruby.AST
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.internal.DataFlowDispatch
/**
* Provides flow summaries for the `Hash` class.
*
* The summaries are ordered (and implemented) based on
* https://docs.ruby-lang.org/en/3.1/Hash.html.
*
* Some summaries are shared with the `Array` class, and those are defined
* in `Array.qll`.
*/
module Hash {
// cannot use API graphs due to negative recursion
private predicate isHashLiteralPair(Pair pair, ConstantValue cv) {
cv = DataFlow::Content::getKnownElementIndex(pair.getKey()) and
pair = any(MethodCall mc | mc.getMethodName() = "[]").getAnArgument()
}
private class HashLiteralSymbolSummary extends SummarizedCallable {
private ConstantValue::ConstantSymbolValue symbol;
HashLiteralSymbolSummary() {
isHashLiteralPair(_, symbol) and
this = "Hash.[:" + symbol.serialize() + "]"
}
final override MethodCall getACall() {
result = API::getTopLevelMember("Hash").getAMethodCall("[]").getExprNode().getExpr() and
exists(result.getKeywordArgument(symbol.getSymbol()))
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
// { symbol: x }
input = "Argument[" + symbol.getSymbol() + ":]" and
output = "ReturnValue.Element[" + symbol.serialize() + "]" and
preservesValue = true
}
}
private class HashLiteralNonSymbolSummary extends SummarizedCallable {
private ConstantValue cv;
HashLiteralNonSymbolSummary() {
this = "Hash.[]" and
isHashLiteralPair(_, cv) and
not cv.isSymbol(_)
}
final override MethodCall getACall() {
result = API::getTopLevelMember("Hash").getAMethodCall("[]").getExprNode().getExpr() and
isHashLiteralPair(result.getAnArgument(), cv)
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
// { 'nonsymbol' => x }
input = "Argument[0..].PairValue[" + cv.serialize() + "]" and
output = "ReturnValue.Element[" + cv.serialize() + "]" and
preservesValue = true
}
}
private class HashNewSummary extends SummarizedCallable {
HashNewSummary() { this = "Hash[]" }
final override ElementReference getACall() {
result.getReceiver() = API::getTopLevelMember("Hash").getAUse().asExpr().getExpr() and
result.getNumberOfArguments() = 1
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
// Hash[{symbol: x}]
input = "Argument[0].WithElement[any]" and
output = "ReturnValue"
or
// Hash[[:symbol, x]]
input = "Argument[0].Element[any].Element[1]" and
output = "ReturnValue.Element[?]"
) and
preservesValue = true
}
}
private class HashNewSummary2 extends SummarizedCallable {
private int i;
private ConstantValue cv;
HashNewSummary2() {
this = "Hash[" + i + ", " + cv.serialize() + "]" and
i % 2 = 1 and
exists(ElementReference er |
cv = er.getArgument(i - 1).getConstantValue() and
exists(er.getArgument(i))
)
}
final override ElementReference getACall() {
result.getReceiver() = API::getTopLevelMember("Hash").getAUse().asExpr().getExpr() and
cv = result.getArgument(i - 1).getConstantValue() and
exists(result.getArgument(i))
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
// Hash[:symbol, x]
input = "Argument[" + i + "]" and
output = "ReturnValue.Element[" + cv.serialize() + "]" and
preservesValue = true
}
}
private class TryConvertSummary extends SummarizedCallable {
TryConvertSummary() { this = "Hash.try_convert" }
override MethodCall getACall() {
result = API::getTopLevelMember("Hash").getAMethodCall("try_convert").getExprNode().getExpr()
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0].WithElement[any]" and
output = "ReturnValue" and
preservesValue = true
}
}
abstract private class StoreSummary extends SummarizedCallable {
MethodCall mc;
bindingset[this]
StoreSummary() { mc.getMethodName() = "store" and mc.getNumberOfArguments() = 2 }
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[1]" and
output = "ReturnValue" and
preservesValue = true
}
}
private class StoreKnownSummary extends StoreSummary {
private ConstantValue cv;
StoreKnownSummary() {
cv = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)) and
this = "store(" + cv.serialize() + ")"
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
super.propagatesFlowExt(input, output, preservesValue)
or
input = "Argument[1]" and
output = "Argument[self].Element[" + cv.serialize() + "]" and
preservesValue = true
or
input = "Argument[self].WithoutElement[" + cv.serialize() + "]" and
output = "Argument[self]" and
preservesValue = true
}
}
private class StoreUnknownSummary extends StoreSummary {
StoreUnknownSummary() {
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0))) and
this = "store"
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
super.propagatesFlowExt(input, output, preservesValue)
or
input = "Argument[1]" and
output = "Argument[self].Element[?]" and
preservesValue = true
}
}
abstract private class AssocSummary extends SummarizedCallable {
MethodCall mc;
bindingset[this]
AssocSummary() { mc.getMethodName() = "assoc" }
override MethodCall getACall() { result = mc }
}
private class AssocKnownSummary extends AssocSummary {
private ConstantValue cv;
AssocKnownSummary() {
this = "assoc(" + cv.serialize() + "]" and
not cv.isInt(_) and // exclude arrays
mc.getNumberOfArguments() = 1 and
cv = DataFlow::Content::getKnownElementIndex(mc.getArgument(0))
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[" + cv.serialize() + ",?]" and
output = "ReturnValue.Element[1]" and
preservesValue = true
}
}
private class AssocUnknownSummary extends AssocSummary {
AssocUnknownSummary() {
this = "assoc" and
mc.getNumberOfArguments() = 1 and
not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0)))
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any].WithoutElement[any]" and
output = "ReturnValue.Element[1]" and
preservesValue = true
}
}
private class EachPairSummary extends SimpleSummarizedCallable {
EachPairSummary() { this = "each_pair" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[1]"
or
input = "Argument[self].WithElement[any]" and
output = "ReturnValue"
) and
preservesValue = true
}
}
private class EachValueSummary extends SimpleSummarizedCallable {
EachValueSummary() { this = "each_value" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[0]"
or
input = "Argument[self].WithElement[any]" and
output = "ReturnValue"
) and
preservesValue = true
}
}
private string getExceptComponent(MethodCall mc, int i) {
mc.getMethodName() = "except" and
result = DataFlow::Content::getKnownElementIndex(mc.getArgument(i)).serialize()
}
private class ExceptSummary extends SummarizedCallable {
MethodCall mc;
ExceptSummary() {
mc.getMethodName() = "except" and
this =
"except(" + concat(int i, string s | s = getExceptComponent(mc, i) | s, "," order by i) +
")"
}
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input =
"Argument[self]" +
concat(int i, string s |
s = getExceptComponent(mc, i)
|
".WithoutElement[" + s + "]" order by i
) and
output = "ReturnValue" and
preservesValue = true
}
}
}
abstract private class FetchValuesSummary extends SummarizedCallable {
MethodCall mc;
bindingset[this]
FetchValuesSummary() { mc.getMethodName() = "fetch_values" }
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[self].WithElement[?]" and
output = "ReturnValue"
or
input = "Argument[0]" and
output = "Argument[block].Parameter[0]"
or
input = "Argument[block].ReturnValue" and
output = "ReturnValue.Element[?]"
) and
preservesValue = true
}
}
private class FetchValuesKnownSummary extends FetchValuesSummary {
ConstantValue cv;
FetchValuesKnownSummary() {
forex(Expr arg | arg = mc.getAnArgument() | exists(arg.getConstantValue())) and
cv = mc.getAnArgument().getConstantValue() and
this = "fetch_values(" + cv.serialize() + ")"
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
super.propagatesFlowExt(input, output, preservesValue)
or
input = "Argument[self].Element[" + cv.serialize() + "]" and
output = "ReturnValue.Element[?]" and
preservesValue = true
}
}
private class FetchValuesUnknownSummary extends FetchValuesSummary {
FetchValuesUnknownSummary() {
exists(Expr arg | arg = mc.getAnArgument() | not exists(arg.getConstantValue())) and
this = "fetch_values(?)"
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
super.propagatesFlowExt(input, output, preservesValue)
or
input = "Argument[self].Element[any]" and
output = "ReturnValue.Element[?]" and
preservesValue = true
}
}
private class MergeSummary extends SimpleSummarizedCallable {
MergeSummary() { this = "merge" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[any].WithElement[any]" and
output = "ReturnValue"
or
input = "Argument[any].Element[any]" and
output = "Argument[block].Parameter[1,2]"
) and
preservesValue = true
}
}
private class MergeBangSummary extends SimpleSummarizedCallable {
MergeBangSummary() { this = ["merge!", "update"] }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[any].WithElement[any]" and
output = ["ReturnValue", "Argument[self]"]
or
input = "Argument[any].Element[any]" and
output = "Argument[block].Parameter[1,2]"
) and
preservesValue = true
}
}
private class RassocSummary extends SimpleSummarizedCallable {
RassocSummary() { this = "rassoc" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any].WithoutElement[any]" and
output = "ReturnValue.Element[1]" and
preservesValue = true
}
}
abstract private class SliceSummary extends SummarizedCallable {
MethodCall mc;
bindingset[this]
SliceSummary() { mc.getMethodName() = "slice" }
final override MethodCall getACall() { result = mc }
}
private class SliceKnownSummary extends SliceSummary {
ConstantValue cv;
SliceKnownSummary() {
cv = mc.getAnArgument().getConstantValue() and
this = "slice(" + cv.serialize() + ")" and
not cv.isInt(_) // covered in `Array.qll`
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].WithElement[?," + cv.serialize() + "]" and
output = "ReturnValue" and
preservesValue = true
}
}
private class SliceUnknownSummary extends SliceSummary {
SliceUnknownSummary() {
exists(Expr arg | arg = mc.getAnArgument() | not exists(arg.getConstantValue())) and
this = "slice(?)"
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].WithoutElement[0..].WithElement[any]" and
output = "ReturnValue" and
preservesValue = true
}
}
private class ToASummary extends SimpleSummarizedCallable {
ToASummary() { this = "to_a" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].WithoutElement[0..].Element[any]" and
output = "ReturnValue.Element[?].Element[1]" and
preservesValue = true
}
}
private class ToHWithoutBlockSummary extends SimpleSummarizedCallable {
ToHWithoutBlockSummary() { this = ["to_h", "to_hash"] and not exists(mc.getBlock()) }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].WithElement[any]" and
output = "ReturnValue" and
preservesValue = true
}
}
private class ToHWithBlockSummary extends SimpleSummarizedCallable {
ToHWithBlockSummary() { this = "to_h" and exists(mc.getBlock()) }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[1]"
or
input = "Argument[block].ReturnValue.Element[1]" and
output = "ReturnValue.Element[?]"
) and
preservesValue = true
}
}
private class TransformKeysSummary extends SimpleSummarizedCallable {
TransformKeysSummary() { this = "transform_keys" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output = "ReturnValue.Element[?]" and
preservesValue = true
}
}
private class TransformKeysBangSummary extends SimpleSummarizedCallable {
TransformKeysBangSummary() { this = "transform_keys!" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[self].Element[any]" and
output = "Argument[self].Element[?]"
or
input = "Argument[self].WithoutElement[any]" and
output = "Argument[self]"
) and
preservesValue = true
}
}
private class TransformValuesSummary extends SimpleSummarizedCallable {
TransformValuesSummary() { this = "transform_values" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[0]"
or
input = "Argument[block].ReturnValue" and
output = "ReturnValue.Element[?]"
) and
preservesValue = true
}
}
private class TransformValuesBangSummary extends SimpleSummarizedCallable {
TransformValuesBangSummary() { this = "transform_values!" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
input = "Argument[self].Element[any]" and
output = "Argument[block].Parameter[0]"
or
input = "Argument[block].ReturnValue" and
output = "Argument[self].Element[?]"
or
input = "Argument[self].WithoutElement[any]" and
output = "Argument[self]"
) and
preservesValue = true
}
}
private class ValuesSummary extends SimpleSummarizedCallable {
ValuesSummary() { this = "values" }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
output = "ReturnValue.Element[?]" and
preservesValue = true
}
}
abstract private class ValuesAtSummary extends SummarizedCallable {
MethodCall mc;
bindingset[this]
ValuesAtSummary() { mc.getMethodName() = "values_at" }
final override MethodCall getACall() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].WithElement[?]" and
output = "ReturnValue" and
preservesValue = true
}
}

View File

@@ -332,16 +332,16 @@ edges
| array_flow.rb:178:16:178:16 | c [element 1] : | array_flow.rb:179:11:179:11 | d [element 2, element 1] : |
| array_flow.rb:178:16:178:16 | c [element 1] : | array_flow.rb:180:11:180:11 | d [element 2, element 1] : |
| array_flow.rb:178:16:178:16 | c [element 1] : | array_flow.rb:180:11:180:11 | d [element 2, element 1] : |
| array_flow.rb:179:11:179:11 | d [element 2, element 1] : | array_flow.rb:179:11:179:22 | call to assoc [element] : |
| array_flow.rb:179:11:179:11 | d [element 2, element 1] : | array_flow.rb:179:11:179:22 | call to assoc [element] : |
| array_flow.rb:179:11:179:22 | call to assoc [element] : | array_flow.rb:179:11:179:25 | ...[...] : |
| array_flow.rb:179:11:179:22 | call to assoc [element] : | array_flow.rb:179:11:179:25 | ...[...] : |
| array_flow.rb:179:11:179:11 | d [element 2, element 1] : | array_flow.rb:179:11:179:22 | call to assoc [element 1] : |
| array_flow.rb:179:11:179:11 | d [element 2, element 1] : | array_flow.rb:179:11:179:22 | call to assoc [element 1] : |
| array_flow.rb:179:11:179:22 | call to assoc [element 1] : | array_flow.rb:179:11:179:25 | ...[...] : |
| array_flow.rb:179:11:179:22 | call to assoc [element 1] : | array_flow.rb:179:11:179:25 | ...[...] : |
| array_flow.rb:179:11:179:25 | ...[...] : | array_flow.rb:179:10:179:26 | ( ... ) |
| array_flow.rb:179:11:179:25 | ...[...] : | array_flow.rb:179:10:179:26 | ( ... ) |
| array_flow.rb:180:11:180:11 | d [element 2, element 1] : | array_flow.rb:180:11:180:22 | call to assoc [element] : |
| array_flow.rb:180:11:180:11 | d [element 2, element 1] : | array_flow.rb:180:11:180:22 | call to assoc [element] : |
| array_flow.rb:180:11:180:22 | call to assoc [element] : | array_flow.rb:180:11:180:25 | ...[...] : |
| array_flow.rb:180:11:180:22 | call to assoc [element] : | array_flow.rb:180:11:180:25 | ...[...] : |
| array_flow.rb:180:11:180:11 | d [element 2, element 1] : | array_flow.rb:180:11:180:22 | call to assoc [element 1] : |
| array_flow.rb:180:11:180:11 | d [element 2, element 1] : | array_flow.rb:180:11:180:22 | call to assoc [element 1] : |
| array_flow.rb:180:11:180:22 | call to assoc [element 1] : | array_flow.rb:180:11:180:25 | ...[...] : |
| array_flow.rb:180:11:180:22 | call to assoc [element 1] : | array_flow.rb:180:11:180:25 | ...[...] : |
| array_flow.rb:180:11:180:25 | ...[...] : | array_flow.rb:180:10:180:26 | ( ... ) |
| array_flow.rb:180:11:180:25 | ...[...] : | array_flow.rb:180:10:180:26 | ( ... ) |
| array_flow.rb:184:13:184:22 | call to source : | array_flow.rb:186:10:186:10 | a [element 1] : |
@@ -520,26 +520,12 @@ edges
| array_flow.rb:312:10:312:10 | b [element 2] : | array_flow.rb:312:10:312:13 | ...[...] |
| array_flow.rb:316:16:316:27 | call to source : | array_flow.rb:317:9:317:9 | a [element 2] : |
| array_flow.rb:316:16:316:27 | call to source : | array_flow.rb:317:9:317:9 | a [element 2] : |
| array_flow.rb:317:9:317:9 | [post] a [element] : | array_flow.rb:319:10:319:10 | a [element] : |
| array_flow.rb:317:9:317:9 | [post] a [element] : | array_flow.rb:319:10:319:10 | a [element] : |
| array_flow.rb:317:9:317:9 | [post] a [element] : | array_flow.rb:320:10:320:10 | a [element] : |
| array_flow.rb:317:9:317:9 | [post] a [element] : | array_flow.rb:320:10:320:10 | a [element] : |
| array_flow.rb:317:9:317:9 | [post] a [element] : | array_flow.rb:321:10:321:10 | a [element] : |
| array_flow.rb:317:9:317:9 | [post] a [element] : | array_flow.rb:321:10:321:10 | a [element] : |
| array_flow.rb:317:9:317:9 | a [element 2] : | array_flow.rb:317:9:317:9 | [post] a [element] : |
| array_flow.rb:317:9:317:9 | a [element 2] : | array_flow.rb:317:9:317:9 | [post] a [element] : |
| array_flow.rb:317:9:317:9 | a [element 2] : | array_flow.rb:317:9:317:36 | call to delete : |
| array_flow.rb:317:9:317:9 | a [element 2] : | array_flow.rb:317:9:317:36 | call to delete : |
| array_flow.rb:317:9:317:36 | call to delete : | array_flow.rb:318:10:318:10 | b |
| array_flow.rb:317:9:317:36 | call to delete : | array_flow.rb:318:10:318:10 | b |
| array_flow.rb:317:23:317:34 | call to source : | array_flow.rb:317:9:317:36 | call to delete : |
| array_flow.rb:317:23:317:34 | call to source : | array_flow.rb:317:9:317:36 | call to delete : |
| array_flow.rb:319:10:319:10 | a [element] : | array_flow.rb:319:10:319:13 | ...[...] |
| array_flow.rb:319:10:319:10 | a [element] : | array_flow.rb:319:10:319:13 | ...[...] |
| array_flow.rb:320:10:320:10 | a [element] : | array_flow.rb:320:10:320:13 | ...[...] |
| array_flow.rb:320:10:320:10 | a [element] : | array_flow.rb:320:10:320:13 | ...[...] |
| array_flow.rb:321:10:321:10 | a [element] : | array_flow.rb:321:10:321:13 | ...[...] |
| array_flow.rb:321:10:321:10 | a [element] : | array_flow.rb:321:10:321:13 | ...[...] |
| array_flow.rb:325:16:325:27 | call to source : | array_flow.rb:326:9:326:9 | a [element 2] : |
| array_flow.rb:325:16:325:27 | call to source : | array_flow.rb:326:9:326:9 | a [element 2] : |
| array_flow.rb:325:30:325:41 | call to source : | array_flow.rb:326:9:326:9 | a [element 3] : |
@@ -578,6 +564,8 @@ edges
| array_flow.rb:334:10:334:10 | a [element] : | array_flow.rb:334:10:334:13 | ...[...] |
| array_flow.rb:338:16:338:25 | call to source : | array_flow.rb:339:9:339:9 | a [element 2] : |
| array_flow.rb:338:16:338:25 | call to source : | array_flow.rb:339:9:339:9 | a [element 2] : |
| array_flow.rb:338:16:338:25 | call to source : | array_flow.rb:345:10:345:10 | a [element 2] : |
| array_flow.rb:338:16:338:25 | call to source : | array_flow.rb:345:10:345:10 | a [element 2] : |
| array_flow.rb:339:9:339:9 | [post] a [element] : | array_flow.rb:343:10:343:10 | a [element] : |
| array_flow.rb:339:9:339:9 | [post] a [element] : | array_flow.rb:343:10:343:10 | a [element] : |
| array_flow.rb:339:9:339:9 | [post] a [element] : | array_flow.rb:344:10:344:10 | a [element] : |
@@ -600,6 +588,8 @@ edges
| array_flow.rb:343:10:343:10 | a [element] : | array_flow.rb:343:10:343:13 | ...[...] |
| array_flow.rb:344:10:344:10 | a [element] : | array_flow.rb:344:10:344:13 | ...[...] |
| array_flow.rb:344:10:344:10 | a [element] : | array_flow.rb:344:10:344:13 | ...[...] |
| array_flow.rb:345:10:345:10 | a [element 2] : | array_flow.rb:345:10:345:13 | ...[...] |
| array_flow.rb:345:10:345:10 | a [element 2] : | array_flow.rb:345:10:345:13 | ...[...] |
| array_flow.rb:345:10:345:10 | a [element] : | array_flow.rb:345:10:345:13 | ...[...] |
| array_flow.rb:345:10:345:10 | a [element] : | array_flow.rb:345:10:345:13 | ...[...] |
| array_flow.rb:349:16:349:25 | call to source : | array_flow.rb:350:9:350:9 | a [element 2] : |
@@ -1703,14 +1693,14 @@ edges
| array_flow.rb:945:16:945:16 | c [element 0] : | array_flow.rb:946:10:946:10 | d [element 2, element 0] : |
| array_flow.rb:945:16:945:16 | c [element 0] : | array_flow.rb:947:10:947:10 | d [element 2, element 0] : |
| array_flow.rb:945:16:945:16 | c [element 0] : | array_flow.rb:947:10:947:10 | d [element 2, element 0] : |
| array_flow.rb:946:10:946:10 | d [element 2, element 0] : | array_flow.rb:946:10:946:22 | call to rassoc [element] : |
| array_flow.rb:946:10:946:10 | d [element 2, element 0] : | array_flow.rb:946:10:946:22 | call to rassoc [element] : |
| array_flow.rb:946:10:946:22 | call to rassoc [element] : | array_flow.rb:946:10:946:25 | ...[...] |
| array_flow.rb:946:10:946:22 | call to rassoc [element] : | array_flow.rb:946:10:946:25 | ...[...] |
| array_flow.rb:947:10:947:10 | d [element 2, element 0] : | array_flow.rb:947:10:947:22 | call to rassoc [element] : |
| array_flow.rb:947:10:947:10 | d [element 2, element 0] : | array_flow.rb:947:10:947:22 | call to rassoc [element] : |
| array_flow.rb:947:10:947:22 | call to rassoc [element] : | array_flow.rb:947:10:947:25 | ...[...] |
| array_flow.rb:947:10:947:22 | call to rassoc [element] : | array_flow.rb:947:10:947:25 | ...[...] |
| array_flow.rb:946:10:946:10 | d [element 2, element 0] : | array_flow.rb:946:10:946:22 | call to rassoc [element 0] : |
| array_flow.rb:946:10:946:10 | d [element 2, element 0] : | array_flow.rb:946:10:946:22 | call to rassoc [element 0] : |
| array_flow.rb:946:10:946:22 | call to rassoc [element 0] : | array_flow.rb:946:10:946:25 | ...[...] |
| array_flow.rb:946:10:946:22 | call to rassoc [element 0] : | array_flow.rb:946:10:946:25 | ...[...] |
| array_flow.rb:947:10:947:10 | d [element 2, element 0] : | array_flow.rb:947:10:947:22 | call to rassoc [element 0] : |
| array_flow.rb:947:10:947:10 | d [element 2, element 0] : | array_flow.rb:947:10:947:22 | call to rassoc [element 0] : |
| array_flow.rb:947:10:947:22 | call to rassoc [element 0] : | array_flow.rb:947:10:947:25 | ...[...] |
| array_flow.rb:947:10:947:22 | call to rassoc [element 0] : | array_flow.rb:947:10:947:25 | ...[...] |
| array_flow.rb:951:10:951:21 | call to source : | array_flow.rb:952:9:952:9 | a [element 0] : |
| array_flow.rb:951:10:951:21 | call to source : | array_flow.rb:952:9:952:9 | a [element 0] : |
| array_flow.rb:951:10:951:21 | call to source : | array_flow.rb:957:9:957:9 | a [element 0] : |
@@ -3277,8 +3267,12 @@ edges
| array_flow.rb:1574:10:1574:10 | b [element] : | array_flow.rb:1574:10:1574:13 | ...[...] |
| array_flow.rb:1576:9:1576:9 | a [element 1] : | array_flow.rb:1576:9:1576:28 | call to values_at [element] : |
| array_flow.rb:1576:9:1576:9 | a [element 1] : | array_flow.rb:1576:9:1576:28 | call to values_at [element] : |
| array_flow.rb:1576:9:1576:9 | a [element 3] : | array_flow.rb:1576:9:1576:28 | call to values_at [element 1] : |
| array_flow.rb:1576:9:1576:9 | a [element 3] : | array_flow.rb:1576:9:1576:28 | call to values_at [element 1] : |
| array_flow.rb:1576:9:1576:9 | a [element 3] : | array_flow.rb:1576:9:1576:28 | call to values_at [element] : |
| array_flow.rb:1576:9:1576:9 | a [element 3] : | array_flow.rb:1576:9:1576:28 | call to values_at [element] : |
| array_flow.rb:1576:9:1576:28 | call to values_at [element 1] : | array_flow.rb:1578:10:1578:10 | b [element 1] : |
| array_flow.rb:1576:9:1576:28 | call to values_at [element 1] : | array_flow.rb:1578:10:1578:10 | b [element 1] : |
| array_flow.rb:1576:9:1576:28 | call to values_at [element] : | array_flow.rb:1577:10:1577:10 | b [element] : |
| array_flow.rb:1576:9:1576:28 | call to values_at [element] : | array_flow.rb:1577:10:1577:10 | b [element] : |
| array_flow.rb:1576:9:1576:28 | call to values_at [element] : | array_flow.rb:1578:10:1578:10 | b [element] : |
@@ -3289,6 +3283,8 @@ edges
| array_flow.rb:1576:9:1576:28 | call to values_at [element] : | array_flow.rb:1580:10:1580:10 | b [element] : |
| array_flow.rb:1577:10:1577:10 | b [element] : | array_flow.rb:1577:10:1577:13 | ...[...] |
| array_flow.rb:1577:10:1577:10 | b [element] : | array_flow.rb:1577:10:1577:13 | ...[...] |
| array_flow.rb:1578:10:1578:10 | b [element 1] : | array_flow.rb:1578:10:1578:13 | ...[...] |
| array_flow.rb:1578:10:1578:10 | b [element 1] : | array_flow.rb:1578:10:1578:13 | ...[...] |
| array_flow.rb:1578:10:1578:10 | b [element] : | array_flow.rb:1578:10:1578:13 | ...[...] |
| array_flow.rb:1578:10:1578:10 | b [element] : | array_flow.rb:1578:10:1578:13 | ...[...] |
| array_flow.rb:1579:10:1579:10 | b [element] : | array_flow.rb:1579:10:1579:13 | ...[...] |
@@ -3748,16 +3744,16 @@ nodes
| array_flow.rb:179:10:179:26 | ( ... ) | semmle.label | ( ... ) |
| array_flow.rb:179:11:179:11 | d [element 2, element 1] : | semmle.label | d [element 2, element 1] : |
| array_flow.rb:179:11:179:11 | d [element 2, element 1] : | semmle.label | d [element 2, element 1] : |
| array_flow.rb:179:11:179:22 | call to assoc [element] : | semmle.label | call to assoc [element] : |
| array_flow.rb:179:11:179:22 | call to assoc [element] : | semmle.label | call to assoc [element] : |
| array_flow.rb:179:11:179:22 | call to assoc [element 1] : | semmle.label | call to assoc [element 1] : |
| array_flow.rb:179:11:179:22 | call to assoc [element 1] : | semmle.label | call to assoc [element 1] : |
| array_flow.rb:179:11:179:25 | ...[...] : | semmle.label | ...[...] : |
| array_flow.rb:179:11:179:25 | ...[...] : | semmle.label | ...[...] : |
| array_flow.rb:180:10:180:26 | ( ... ) | semmle.label | ( ... ) |
| array_flow.rb:180:10:180:26 | ( ... ) | semmle.label | ( ... ) |
| array_flow.rb:180:11:180:11 | d [element 2, element 1] : | semmle.label | d [element 2, element 1] : |
| array_flow.rb:180:11:180:11 | d [element 2, element 1] : | semmle.label | d [element 2, element 1] : |
| array_flow.rb:180:11:180:22 | call to assoc [element] : | semmle.label | call to assoc [element] : |
| array_flow.rb:180:11:180:22 | call to assoc [element] : | semmle.label | call to assoc [element] : |
| array_flow.rb:180:11:180:22 | call to assoc [element 1] : | semmle.label | call to assoc [element 1] : |
| array_flow.rb:180:11:180:22 | call to assoc [element 1] : | semmle.label | call to assoc [element 1] : |
| array_flow.rb:180:11:180:25 | ...[...] : | semmle.label | ...[...] : |
| array_flow.rb:180:11:180:25 | ...[...] : | semmle.label | ...[...] : |
| array_flow.rb:184:13:184:22 | call to source : | semmle.label | call to source : |
@@ -3970,8 +3966,6 @@ nodes
| array_flow.rb:312:10:312:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:316:16:316:27 | call to source : | semmle.label | call to source : |
| array_flow.rb:316:16:316:27 | call to source : | semmle.label | call to source : |
| array_flow.rb:317:9:317:9 | [post] a [element] : | semmle.label | [post] a [element] : |
| array_flow.rb:317:9:317:9 | [post] a [element] : | semmle.label | [post] a [element] : |
| array_flow.rb:317:9:317:9 | a [element 2] : | semmle.label | a [element 2] : |
| array_flow.rb:317:9:317:9 | a [element 2] : | semmle.label | a [element 2] : |
| array_flow.rb:317:9:317:36 | call to delete : | semmle.label | call to delete : |
@@ -3980,18 +3974,6 @@ nodes
| array_flow.rb:317:23:317:34 | call to source : | semmle.label | call to source : |
| array_flow.rb:318:10:318:10 | b | semmle.label | b |
| array_flow.rb:318:10:318:10 | b | semmle.label | b |
| array_flow.rb:319:10:319:10 | a [element] : | semmle.label | a [element] : |
| array_flow.rb:319:10:319:10 | a [element] : | semmle.label | a [element] : |
| array_flow.rb:319:10:319:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:319:10:319:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:320:10:320:10 | a [element] : | semmle.label | a [element] : |
| array_flow.rb:320:10:320:10 | a [element] : | semmle.label | a [element] : |
| array_flow.rb:320:10:320:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:320:10:320:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:321:10:321:10 | a [element] : | semmle.label | a [element] : |
| array_flow.rb:321:10:321:10 | a [element] : | semmle.label | a [element] : |
| array_flow.rb:321:10:321:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:321:10:321:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:325:16:325:27 | call to source : | semmle.label | call to source : |
| array_flow.rb:325:16:325:27 | call to source : | semmle.label | call to source : |
| array_flow.rb:325:30:325:41 | call to source : | semmle.label | call to source : |
@@ -4056,6 +4038,8 @@ nodes
| array_flow.rb:344:10:344:10 | a [element] : | semmle.label | a [element] : |
| array_flow.rb:344:10:344:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:344:10:344:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:345:10:345:10 | a [element 2] : | semmle.label | a [element 2] : |
| array_flow.rb:345:10:345:10 | a [element 2] : | semmle.label | a [element 2] : |
| array_flow.rb:345:10:345:10 | a [element] : | semmle.label | a [element] : |
| array_flow.rb:345:10:345:10 | a [element] : | semmle.label | a [element] : |
| array_flow.rb:345:10:345:13 | ...[...] | semmle.label | ...[...] |
@@ -5278,14 +5262,14 @@ nodes
| array_flow.rb:945:16:945:16 | c [element 0] : | semmle.label | c [element 0] : |
| array_flow.rb:946:10:946:10 | d [element 2, element 0] : | semmle.label | d [element 2, element 0] : |
| array_flow.rb:946:10:946:10 | d [element 2, element 0] : | semmle.label | d [element 2, element 0] : |
| array_flow.rb:946:10:946:22 | call to rassoc [element] : | semmle.label | call to rassoc [element] : |
| array_flow.rb:946:10:946:22 | call to rassoc [element] : | semmle.label | call to rassoc [element] : |
| array_flow.rb:946:10:946:22 | call to rassoc [element 0] : | semmle.label | call to rassoc [element 0] : |
| array_flow.rb:946:10:946:22 | call to rassoc [element 0] : | semmle.label | call to rassoc [element 0] : |
| array_flow.rb:946:10:946:25 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:946:10:946:25 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:947:10:947:10 | d [element 2, element 0] : | semmle.label | d [element 2, element 0] : |
| array_flow.rb:947:10:947:10 | d [element 2, element 0] : | semmle.label | d [element 2, element 0] : |
| array_flow.rb:947:10:947:22 | call to rassoc [element] : | semmle.label | call to rassoc [element] : |
| array_flow.rb:947:10:947:22 | call to rassoc [element] : | semmle.label | call to rassoc [element] : |
| array_flow.rb:947:10:947:22 | call to rassoc [element 0] : | semmle.label | call to rassoc [element 0] : |
| array_flow.rb:947:10:947:22 | call to rassoc [element 0] : | semmle.label | call to rassoc [element 0] : |
| array_flow.rb:947:10:947:25 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:947:10:947:25 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:951:10:951:21 | call to source : | semmle.label | call to source : |
@@ -6910,12 +6894,16 @@ nodes
| array_flow.rb:1576:9:1576:9 | a [element 1] : | semmle.label | a [element 1] : |
| array_flow.rb:1576:9:1576:9 | a [element 3] : | semmle.label | a [element 3] : |
| array_flow.rb:1576:9:1576:9 | a [element 3] : | semmle.label | a [element 3] : |
| array_flow.rb:1576:9:1576:28 | call to values_at [element 1] : | semmle.label | call to values_at [element 1] : |
| array_flow.rb:1576:9:1576:28 | call to values_at [element 1] : | semmle.label | call to values_at [element 1] : |
| array_flow.rb:1576:9:1576:28 | call to values_at [element] : | semmle.label | call to values_at [element] : |
| array_flow.rb:1576:9:1576:28 | call to values_at [element] : | semmle.label | call to values_at [element] : |
| array_flow.rb:1577:10:1577:10 | b [element] : | semmle.label | b [element] : |
| array_flow.rb:1577:10:1577:10 | b [element] : | semmle.label | b [element] : |
| array_flow.rb:1577:10:1577:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:1577:10:1577:13 | ...[...] | semmle.label | ...[...] |
| array_flow.rb:1578:10:1578:10 | b [element 1] : | semmle.label | b [element 1] : |
| array_flow.rb:1578:10:1578:10 | b [element 1] : | semmle.label | b [element 1] : |
| array_flow.rb:1578:10:1578:10 | b [element] : | semmle.label | b [element] : |
| array_flow.rb:1578:10:1578:10 | b [element] : | semmle.label | b [element] : |
| array_flow.rb:1578:10:1578:13 | ...[...] | semmle.label | ...[...] |
@@ -7125,9 +7113,6 @@ subpaths
| array_flow.rb:312:10:312:13 | ...[...] | array_flow.rb:308:16:308:25 | call to source : | array_flow.rb:312:10:312:13 | ...[...] | $@ | array_flow.rb:308:16:308:25 | call to source : | call to source : |
| array_flow.rb:318:10:318:10 | b | array_flow.rb:316:16:316:27 | call to source : | array_flow.rb:318:10:318:10 | b | $@ | array_flow.rb:316:16:316:27 | call to source : | call to source : |
| array_flow.rb:318:10:318:10 | b | array_flow.rb:317:23:317:34 | call to source : | array_flow.rb:318:10:318:10 | b | $@ | array_flow.rb:317:23:317:34 | call to source : | call to source : |
| array_flow.rb:319:10:319:13 | ...[...] | array_flow.rb:316:16:316:27 | call to source : | array_flow.rb:319:10:319:13 | ...[...] | $@ | array_flow.rb:316:16:316:27 | call to source : | call to source : |
| array_flow.rb:320:10:320:13 | ...[...] | array_flow.rb:316:16:316:27 | call to source : | array_flow.rb:320:10:320:13 | ...[...] | $@ | array_flow.rb:316:16:316:27 | call to source : | call to source : |
| array_flow.rb:321:10:321:13 | ...[...] | array_flow.rb:316:16:316:27 | call to source : | array_flow.rb:321:10:321:13 | ...[...] | $@ | array_flow.rb:316:16:316:27 | call to source : | call to source : |
| array_flow.rb:327:10:327:10 | b | array_flow.rb:325:16:325:27 | call to source : | array_flow.rb:327:10:327:10 | b | $@ | array_flow.rb:325:16:325:27 | call to source : | call to source : |
| array_flow.rb:328:10:328:13 | ...[...] | array_flow.rb:325:30:325:41 | call to source : | array_flow.rb:328:10:328:13 | ...[...] | $@ | array_flow.rb:325:30:325:41 | call to source : | call to source : |
| array_flow.rb:332:10:332:10 | b | array_flow.rb:330:16:330:27 | call to source : | array_flow.rb:332:10:332:10 | b | $@ | array_flow.rb:330:16:330:27 | call to source : | call to source : |

View File

@@ -176,8 +176,8 @@ def m19
b = ["b", 1]
c = ["c", source(19)]
d = [a, b, c]
sink (d.assoc("a")[0]) # $ hasValueFlow=19
sink (d.assoc("c")[0]) # $ hasValueFlow=19
sink (d.assoc("a")[1]) # $ hasValueFlow=19
sink (d.assoc("c")[1]) # $ hasValueFlow=19
end
def m20(i)
@@ -316,9 +316,9 @@ def m36
a = [0, 1, source(36.1)]
b = a.delete(2) { source(36.2) }
sink b # $ hasValueFlow=36.1 $ hasValueFlow=36.2
sink a[0] # $ hasValueFlow=36.1
sink a[1] # $ hasValueFlow=36.1
sink a[2] # $ hasValueFlow=36.1
sink a[0]
sink a[1]
sink a[2]
end
def m37(i)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
/**
* @kind path-problem
*/
import ruby
import TestUtilities.InlineFlowTest
import PathGraph
class HasFlowTest extends InlineFlowTest {
override DataFlow::Configuration getTaintFlowConfig() { none() }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, DefaultValueFlowConf conf
where conf.hasFlowPath(source, sink)
select sink, source, sink, "$@", source, source.toString()

View File

@@ -0,0 +1,725 @@
def taint x
x
end
def sink x
puts x
end
def m1()
hash = {
:a => taint(1.1),
:b => 1,
c: taint(1.2),
d: 2,
'e': taint(1.3),
'f': 3,
'g' => taint(1.4),
'h' => 4,
0 => taint(1.5),
1 => 5
}
sink(hash[:a]) # $ hasValueFlow=1.1
sink(hash[:b])
sink(hash[:c]) # $ hasValueFlow=1.2
sink(hash[:d])
sink(hash['e']) # $ hasValueFlow=1.3
sink(hash['f'])
sink(hash['g']) # $ hasValueFlow=1.4
sink(hash['h'])
sink(hash[0]) # $ hasValueFlow=1.5
sink(hash[1])
end
m1()
def m2()
hash = Hash.new
hash[0] = taint(2.1)
hash[1] = 1
hash[:a] = taint(2.2)
hash[:b] = 2
hash['a'] = taint(2.3)
hash['b'] = 3
sink(hash[0]) # $ hasValueFlow=2.1
sink(hash[1])
sink(hash[:a]) # $ hasValueFlow=2.2
sink(hash[:b])
sink(hash['a']) # $ hasValueFlow=2.3
sink(hash['b'])
end
m2()
def m3()
hash1 = Hash[a: taint(3.1), b: 1]
sink(hash1[:a]) # $ hasValueFlow=3.1
sink(hash1[:b])
x = {a: taint(3.2), b: 1}
hash2 = Hash[x]
sink(hash2[:a]) # $ hasValueFlow=3.2
sink(hash2[:b])
hash3 = Hash[[[:a, taint(3.3)], [:b, 1]]]
sink(hash3[:a]) # $ hasValueFlow=3.3
sink(hash3[:b]) # $ SPURIOUS hasValueFlow=3.3
hash4 = Hash[:a, taint(3.4), :b, 1]
sink(hash4[:a]) # $ hasValueFlow=3.4
sink(hash4[:b])
end
m3()
def m4()
hash1 = ::Hash.[](a: taint(4.1), b: 1)
sink(hash1[:a]) # $ hasValueFlow=4.1
sink(hash1[:b])
end
m4()
def m5()
hash = {
:a => taint(5.1),
:b => 1
}
hash2 = Hash.try_convert(hash)
sink(hash2[:a]) # $ hasValueFlow=5.1
sink(hash2[:b])
end
m5()
def m6()
hash = Hash.new
b = (hash[:a] = taint(6.1))
sink(b) # $ hasValueFlow=6.1
end
m6()
def m7(x)
hash = Hash.new
b = hash.store(:a, taint(7.1))
sink(hash[:a]) # $ hasValueFlow=7.1
sink(b) # $ hasValueFlow=7.1
hash.store(:a, 1)
sink(hash[:a])
c = hash.store(x, taint(7.2))
sink(hash[:a]) # $ hasValueFlow=7.2
sink(hash[10]) # $ hasValueFlow=7.2
sink(c) # $ hasValueFlow=7.2
end
m7("foo")
def m8()
hash = {
:a => taint(8.1),
:b => 1
}
hash.any? { |key_or_value|
sink(key_or_value) # $ hasValueFlow=8.1
}
hash.any? { |key,value|
sink(key)
sink(value) # $ hasValueFlow=8.1
}
end
m8()
def m9(x, y)
hash = {
:a => taint(10.1),
:b => 1
}
b = hash.assoc(:a)
sink(b[0])
sink(b[1]) # $ hasValueFlow=10.1
sink(b[x]) # $ hasValueFlow=10.1
c = hash.assoc(y)
sink(c[1]) # $ hasValueFlow=10.1
end
m9(1, :a)
def m10()
hash = {
:a => taint(9.1),
:b => 1
}
hash.clear
sink(hash[:a])
end
m10()
def m11()
hash = {
:a => taint(11.1),
:b => 1
}
a = hash.compact
sink(a[:a]) # $ hasValueFlow=11.1
sink(a[:b])
end
m11()
def m12()
hash = {
:a => taint(12.1),
:b => 1
}
a = hash.delete(:a)
sink(a) # $ hasValueFlow=12.1
sink(hash[:a])
end
m12()
def m13()
hash = {
:a => taint(13.1),
:b => 1
}
a = hash.delete_if do |key, value|
sink key
sink value # $ hasValueFlow=13.1
end
sink(a[:a]) # $ hasValueFlow=13.1
sink(hash[:a]) # $ hasValueFlow=13.1
sink(hash[0])
end
m13()
def m14()
hash = {
:a => taint(14.1),
:b => 1,
:c => {
:d => taint(14.2),
:e => 2
}
}
sink(hash.dig(:a)) # $ hasValueFlow=14.1
sink(hash.dig(:b))
sink(hash.dig(:c,:d)) # $ hasValueFlow=14.2
sink(hash.dig(:c,:e))
end
m14()
def m15()
hash = {
:a => taint(15.1),
:b => 1
}
x = hash.each do |key, value|
sink key
sink value # $ hasValueFlow=15.1
end
sink(x[:a]) # $ hasValueFlow=15.1
sink(x[:b])
end
m15()
def m16()
hash = {
:a => taint(16.1),
:b => 1
}
x = hash.each_key do |key|
sink key
end
sink(x[:a]) # $ hasValueFlow=16.1
sink(x[:b])
end
m16()
def m17()
hash = {
:a => taint(17.1),
:b => 1
}
x = hash.each_pair do |key, value|
sink key
sink value # $ hasValueFlow=17.1
end
sink(x[:a]) # $ hasValueFlow=17.1
sink(x[:b])
end
m17()
def m18()
hash = {
:a => taint(18.1),
:b => 1
}
x = hash.each_value do |value|
sink value # $ hasValueFlow=18.1
end
sink(x[:a]) # $ hasValueFlow=18.1
sink(x[:b])
end
m18()
def m19(x)
hash = {
:a => taint(19.1),
:b => 1,
:c => taint(19.2),
:d => taint(19.3)
}
x = hash.except(:a,x,:d)
sink(x[:a])
sink(x[:b])
sink(x[:c]) # $ hasValueFlow=19.2
sink(x[:d])
end
m19(:c)
def m20(x)
hash = {
:a => taint(20.1),
:b => 1,
:c => taint(20.2)
}
b = hash.fetch(taint(20.3)) do |x|
sink x # $ hasValueFlow=20.3
end
sink(b) # $ hasValueFlow=20.1 $ hasValueFlow=20.2
b = hash.fetch(:a)
sink b # $ hasValueFlow=20.1
b = hash.fetch(:a, taint(20.4))
sink b # $ hasValueFlow=20.1 $ hasValueFlow=20.4
b = hash.fetch(:b, taint(20.5))
sink b # $ hasValueFlow=20.5
b = hash.fetch(x, taint(20.6))
sink b # $ hasValueFlow=20.1 $ hasValueFlow=20.2 $ hasValueFlow=20.6
end
m20(:a)
def m21(x)
hash = {
:a => taint(21.1),
:b => 1,
:c => taint(21.2)
}
b = hash.fetch_values(taint(21.3)) do |x|
sink x # $ hasValueFlow=21.3
taint(21.4)
end
sink(b[0]) # $ hasValueFlow=21.1 $ hasValueFlow=21.2 $ hasValueFlow=21.4
b = hash.fetch_values(:a)
sink(b[0]) # $ hasValueFlow=21.1
b = hash.fetch_values(:a,x)
sink(b[1]) # $ hasValueFlow=21.1 $ hasValueFlow=21.2
end
m21(:c)
def m22()
hash = {
:a => taint(22.1),
:b => 1,
:c => taint(22.2)
}
b = hash.filter do |key, value|
sink key
sink value # $ hasValueFlow=22.1 $ hasValueFlow=22.2
true
end
sink (b[:a]) # $ hasValueFlow=22.1
end
m22()
def m23()
hash = {
:a => taint(23.1),
:b => 1,
:c => taint(23.2)
}
hash.filter! do |key, value|
sink key
sink value # $ hasValueFlow=23.1 $ hasValueFlow=23.2
true
end
sink (hash[:a]) # $ hasValueFlow=23.1
end
m23()
def m24()
hash = {
:a => taint(24.1),
:b => 1,
:c => taint(24.2)
}
b = hash.flatten
sink (b[1]) # $ hasValueFlow=24.1 $ hasValueFlow=24.2
end
m24()
def m25()
hash = {
:a => taint(25.1),
:b => 1,
:c => taint(25.2)
}
b = hash.keep_if do |key, value|
sink key
sink value # $ hasValueFlow=25.1 $ hasValueFlow=25.2
true
end
sink (hash[:a]) # $ hasValueFlow=25.1
sink (b[:a]) # $ hasValueFlow=25.1
end
m25()
def m26()
hash1 = {
:a => taint(26.1),
:b => 1,
:c => taint(26.2)
}
hash2 = {
:d => taint(26.3),
:e => 1,
:f => taint(26.4)
}
hash = hash1.merge(hash2) do |key, old_value, new_value|
sink key
sink old_value # $ hasValueFlow=26.1 $ hasValueFlow=26.2 $ hasValueFlow=26.3 $ hasValueFlow=26.4
sink new_value # $ hasValueFlow=26.1 $ hasValueFlow=26.2 $ hasValueFlow=26.3 $ hasValueFlow=26.4
end
sink (hash[:a]) # $ hasValueFlow=26.1
sink (hash[:b])
sink (hash[:c]) # $ hasValueFlow=26.2
sink (hash[:d]) # $ hasValueFlow=26.3
sink (hash[:e])
sink (hash[:f]) # $ hasValueFlow=26.4
end
m26()
def m27()
hash1 = {
:a => taint(27.1),
:b => 1,
:c => taint(27.2)
}
hash2 = {
:d => taint(27.3),
:e => 1,
:f => taint(27.4)
}
hash = hash1.merge!(hash2) do |key, old_value, new_value|
sink key
sink old_value # $ hasValueFlow=27.1 $ hasValueFlow=27.2 $ hasValueFlow=27.3 $ hasValueFlow=27.4
sink new_value # $ hasValueFlow=27.1 $ hasValueFlow=27.2 $ hasValueFlow=27.3 $ hasValueFlow=27.4
end
sink (hash[:a]) # $ hasValueFlow=27.1
sink (hash[:b])
sink (hash[:c]) # $ hasValueFlow=27.2
sink (hash[:d]) # $ hasValueFlow=27.3
sink (hash[:e])
sink (hash[:f]) # $ hasValueFlow=27.4
sink (hash1[:a]) # $ hasValueFlow=27.1
sink (hash1[:b])
sink (hash1[:c]) # $ hasValueFlow=27.2
sink (hash1[:d]) # $ hasValueFlow=27.3
sink (hash1[:e])
sink (hash1[:f]) # $ hasValueFlow=27.4
end
m27()
def m28
hash = {
:a => taint(28.1),
:b => 1
}
b = hash.rassoc(0)
sink(b[0])
sink(b[1]) # $ hasValueFlow=28.1
end
m28()
def m29
hash = {
:a => taint(29.1),
:b => 1
}
b = hash.reject do |key,value|
sink key
sink value # $ hasValueFlow=29.1
value > 10
end
sink b[:a] # $ hasValueFlow=29.1
end
m29()
def m30
hash = {
:a => taint(30.1),
:b => 1
}
b = hash.reject! do |key,value|
sink key
sink value # $ hasValueFlow=30.1
value > 10
end
sink b[:a] # $ hasValueFlow=30.1
sink hash[:a] # $ hasValueFlow=30.1
end
m30()
def m31()
hash = {
:a => taint(31.1),
:b => 1,
:c => taint(31.2)
}
hash2 = {
:c => taint(31.3)
}
hash2.replace(hash)
sink (hash2[:a]) # $ hasValueFlow=31.1
sink (hash2[:b])
sink (hash2[:c]) # $ hasValueFlow=31.2
end
def m32()
hash = {
:a => taint(32.1),
:b => 1,
:c => taint(32.2)
}
b = hash.select do |key, value|
sink key
sink value # $ hasValueFlow=32.1 $ hasValueFlow=32.2
true
end
sink (b[:a]) # $ hasValueFlow=32.1
end
m32()
def m33()
hash = {
:a => taint(33.1),
:b => 1,
:c => taint(33.2)
}
hash.select! do |key, value|
sink key
sink value # $ hasValueFlow=33.1 $ hasValueFlow=33.2
true
end
sink (hash[:a]) # $ hasValueFlow=33.1
end
m33()
def m34()
hash = {
:a => taint(34.1),
:b => 1,
:c => taint(34.2)
}
b = hash.shift
sink (hash[:a]) # $ hasValueFlow=34.1
sink (b[0])
sink (b[1]) # $ hasValueFlow=34.1 $ hasValueFlow=34.2
end
m34()
def m35(x)
hash = {
:a => taint(35.1),
:b => 1,
:c => taint(35.2)
}
b = hash.slice(:a, :b)
sink (b[:a]) # $ hasValueFlow=35.1
sink (b[:b])
sink (b[:c])
c = hash.slice(:a, x)
sink (c[:a]) # $ hasValueFlow=35.1
sink (c[:b])
sink (c[:c]) # $ hasValueFlow=35.2
end
m35(:c)
def m36()
hash = {
:a => taint(36.1),
:b => 1,
:c => taint(36.2)
}
a = hash.to_a
sink (a[0][0])
sink (a[0][1]) # $ hasValueFlow=36.1 $ hasValueFlow=36.2
end
m36()
def m37()
hash = {
:a => taint(37.1),
:b => 1,
:c => taint(37.2)
}
a = hash.to_h
sink (a[:a]) # $ hasValueFlow=37.1
sink (a[:b])
sink (a[:c]) # $ hasValueFlow=37.2
b = hash.to_h do |key, value|
sink key
sink value # $ hasValueFlow=37.1 $ hasValueFlow=37.2
[:d, taint(37.3)]
end
sink (b[:d]) # $ hasValueFlow=37.3
end
m37()
def m38()
hash = {
:a => taint(38.1),
:b => 1,
:c => taint(38.2)
}
a = hash.transform_keys {|key| key.to_s }
sink (a["a"]) # $ hasValueFlow=38.1 $ hasValueFlow=38.2
sink (a["b"]) # $ hasValueFlow=38.1 $ hasValueFlow=38.2
sink (a["c"]) # $ hasValueFlow=38.1 $ hasValueFlow=38.2
end
m38()
def m39()
hash = {
:a => taint(39.1),
:b => 1,
:c => taint(39.2)
}
hash.transform_keys! {|key| key.to_s }
sink (hash["a"]) # $ hasValueFlow=39.1 $ hasValueFlow=39.2
sink (hash["b"]) # $ hasValueFlow=39.1 $ hasValueFlow=39.2
sink (hash["c"]) # $ hasValueFlow=39.1 $ hasValueFlow=39.2
end
m39()
def m40()
hash = {
:a => taint(40.1),
:b => 1,
:c => taint(40.2)
}
b = hash.transform_values do |value|
sink value # $ hasValueFlow=40.1 $ hasValueFlow=40.2
taint(40.3)
end
sink (hash[:a]) # $ hasValueFlow=40.1
sink (b[:a]) # $ hasValueFlow=40.3
end
m40()
def m41()
hash = {
:a => taint(41.1),
:b => 1,
:c => taint(41.2)
}
hash.transform_values! do |value|
sink value # $ hasValueFlow=41.1 $ hasValueFlow=41.2
taint(41.3)
end
sink (hash[:a]) # $ hasValueFlow=41.3
end
m41()
def m42()
hash1 = {
:a => taint(42.1),
:b => 1,
:c => taint(42.2)
}
hash2 = {
:d => taint(42.3),
:e => 1,
:f => taint(42.4)
}
hash = hash1.update(hash2) do |key, old_value, new_value|
sink key
sink old_value # $ hasValueFlow=42.1 $ hasValueFlow=42.2 $ hasValueFlow=42.3 $ hasValueFlow=42.4
sink new_value # $ hasValueFlow=42.1 $ hasValueFlow=42.2 $ hasValueFlow=42.3 $ hasValueFlow=42.4
end
sink (hash[:a]) # $ hasValueFlow=42.1
sink (hash[:b])
sink (hash[:c]) # $ hasValueFlow=42.2
sink (hash[:d]) # $ hasValueFlow=42.3
sink (hash[:e])
sink (hash[:f]) # $ hasValueFlow=42.4
sink (hash1[:a]) # $ hasValueFlow=42.1
sink (hash1[:b])
sink (hash1[:c]) # $ hasValueFlow=42.2
sink (hash1[:d]) # $ hasValueFlow=42.3
sink (hash1[:e])
sink (hash1[:f]) # $ hasValueFlow=42.4
end
m42()
def m43()
hash = {
:a => taint(43.1),
:b => 1,
:c => taint(43.2)
}
a = hash.values
sink (a[0]) # $ hasValueFlow=43.1 # $ hasValueFlow=43.2
end
m43()
def m44(x)
hash = {
:a => taint(44.1),
:b => 1,
:c => taint(44.2)
}
b = hash.values_at(:a)
sink(b[0]) # $ hasValueFlow=44.1
b = hash.fetch_values(:a,x)
sink(b[1]) # $ hasValueFlow=44.1 $ hasValueFlow=44.2
end
m44(:c)

View File

@@ -10,6 +10,8 @@ edges
| params_flow.rb:21:27:21:34 | call to taint : | params_flow.rb:16:18:16:19 | p2 : |
| params_flow.rb:22:13:22:20 | call to taint : | params_flow.rb:16:18:16:19 | p2 : |
| params_flow.rb:22:27:22:34 | call to taint : | params_flow.rb:16:13:16:14 | p1 : |
| params_flow.rb:23:16:23:23 | call to taint : | params_flow.rb:16:18:16:19 | p2 : |
| params_flow.rb:23:33:23:40 | call to taint : | params_flow.rb:16:13:16:14 | p1 : |
nodes
| params_flow.rb:9:16:9:17 | p1 : | semmle.label | p1 : |
| params_flow.rb:9:20:9:21 | p2 : | semmle.label | p2 : |
@@ -25,11 +27,15 @@ nodes
| params_flow.rb:21:27:21:34 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:22:13:22:20 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:22:27:22:34 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:23:16:23:23 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:23:33:23:40 | call to taint : | semmle.label | call to taint : |
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 : |
| params_flow.rb:11:10:11:11 | p2 | params_flow.rb:14:22:14:29 | call to taint : | params_flow.rb:11:10:11:11 | p2 | $@ | params_flow.rb:14:22:14:29 | call to taint : | call to taint : |
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:21:13:21:20 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:21:13:21:20 | call to taint : | call to taint : |
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:22:27:22:34 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:22:27:22:34 | call to taint : | call to taint : |
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:23:33:23:40 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:23:33:23:40 | call to taint : | call to taint : |
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:21:27:21:34 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:21:27:21:34 | call to taint : | call to taint : |
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:22:13:22:20 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:22:13:22:20 | call to taint : | call to taint : |
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:23:16:23:23 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:23:16:23:23 | call to taint : | call to taint : |

View File

@@ -14,9 +14,10 @@ end
positional(taint(1), taint(2))
def keyword(p1:, p2:)
sink p1 # $ hasValueFlow=3 $ hasValueFlow=6
sink p2 # $ hasValueFlow=4 $ hasValueFlow=5
sink p1 # $ hasValueFlow=3 $ hasValueFlow=6 $ hasValueFlow=8
sink p2 # $ hasValueFlow=4 $ hasValueFlow=5 $ hasValueFlow=7
end
keyword(p1: taint(3), p2: taint(4))
keyword(p2: taint(5), p1: taint(6))
keyword(:p2 => taint(7), :p1 => taint(8))

View File

@@ -249,7 +249,6 @@ edges
| string_flow.rb:283:9:283:9 | a : | string_flow.rb:283:9:283:14 | ...[...] : |
| string_flow.rb:283:9:283:9 | a : | string_flow.rb:283:9:283:14 | ...[...] [element 0] : |
| string_flow.rb:283:9:283:9 | a : | string_flow.rb:283:9:283:14 | ...[...] [element 1] : |
| string_flow.rb:283:9:283:9 | a : | string_flow.rb:283:9:283:14 | ...[...] [element] : |
| string_flow.rb:283:9:283:9 | a [element 1] : | string_flow.rb:283:9:283:14 | ...[...] [element 0] : |
| string_flow.rb:283:9:283:9 | a [element 2] : | string_flow.rb:283:9:283:14 | ...[...] [element 1] : |
| string_flow.rb:283:9:283:9 | a [element] : | string_flow.rb:283:9:283:14 | ...[...] [element] : |

View File

@@ -14,6 +14,9 @@
| tst-IncompleteHostnameRegExp.rb:17:14:17:30 | test.example.com$ | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.rb:17:13:17:31 | `test.example.com$` | here |
| tst-IncompleteHostnameRegExp.rb:19:14:19:30 | ^test.example.com | This string, which is used as a regular expression $@, has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.rb:20:13:20:26 | "#{...}$" | here |
| tst-IncompleteHostnameRegExp.rb:20:14:20:31 | ^test.example.com$ | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.rb:20:13:20:26 | "#{...}$" | here |
| tst-IncompleteHostnameRegExp.rb:22:24:22:40 | test.example.com$ | This string, which is used as a regular expression $@, has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.rb:23:13:23:29 | ...[...] | here |
| tst-IncompleteHostnameRegExp.rb:28:24:28:40 | test.example.com$ | This string, which is used as a regular expression $@, has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.rb:63:20:63:36 | ...[...] | here |
| tst-IncompleteHostnameRegExp.rb:30:27:30:43 | test.example.com$ | This string, which is used as a regular expression $@, has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.rb:66:20:66:36 | ...[...] | here |
| tst-IncompleteHostnameRegExp.rb:37:3:37:53 | ^(https?:)?\\/\\/((service\|www).)?example.com(?=$\|\\/) | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.rb:37:2:37:54 | /^(https?:)?\\/\\/((service\|www).../ | here |
| tst-IncompleteHostnameRegExp.rb:38:3:38:43 | ^(http\|https):\\/\\/www.example.com\\/p\\/f\\/ | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.rb:38:2:38:44 | /^(http\|https):\\/\\/www.example.../ | here |
| tst-IncompleteHostnameRegExp.rb:39:5:39:30 | http:\\/\\/sub.example.com\\/ | This regular expression has an unescaped '.' before 'example.com', so it might match more hosts than expected. | tst-IncompleteHostnameRegExp.rb:39:2:39:33 | /^(http:\\/\\/sub.example.com\\/)/ | here |