Merge pull request #10375 from asgerf/rb/summarize-loads-v2

Ruby: type-tracking and API edges through simple library callables
This commit is contained in:
Asger F
2022-09-30 14:25:17 +02:00
committed by GitHub
23 changed files with 900 additions and 240 deletions

View File

@@ -9,6 +9,7 @@
private import codeql.ruby.AST
private import codeql.ruby.DataFlow
private import codeql.ruby.typetracking.TypeTracker
private import codeql.ruby.typetracking.TypeTrackerSpecific as TypeTrackerSpecific
private import codeql.ruby.ast.internal.Module
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
@@ -257,6 +258,30 @@ module API {
*/
Node getAnImmediateSubclass() { result = this.getASuccessor(Label::subclass()) }
/**
* Gets a node representing the `content` stored on the base object.
*/
Node getContent(DataFlow::Content content) {
result = this.getASuccessor(Label::content(content))
}
/**
* Gets a node representing the `contents` stored on the base object.
*/
pragma[inline]
Node getContents(DataFlow::ContentSet contents) {
// We always use getAStoreContent when generating the graph, and we always use getAReadContent when querying the graph.
result = this.getContent(contents.getAReadContent())
}
/** Gets a node representing the instance field of the given `name`, which must include the `@` character. */
Node getField(string name) { result = this.getContent(DataFlowPrivate::TFieldContent(name)) }
/** Gets a node representing an element of this collection (known or unknown). */
Node getAnElement() {
result = this.getContents(any(DataFlow::ContentSet set | set.isAnyElement()))
}
/**
* Gets a string representation of the lexicographically least among all shortest access paths
* from the root to this node.
@@ -495,9 +520,25 @@ module API {
ref.asExpr() = c and
read = c.getExpr()
)
or
exists(TypeTrackerSpecific::TypeTrackerContent c |
TypeTrackerSpecific::basicLoadStep(node, ref, c) and
lbl = Label::content(c.getAStoreContent())
)
// note: method calls are not handled here as there is no DataFlow::Node for the intermediate MkMethodAccessNode API node
}
/**
* Holds if `rhs` is a definition of a node that should have an incoming edge labeled `lbl`,
* from a def node that is reachable from `node`.
*/
private predicate defStep(Label::ApiLabel lbl, DataFlow::Node node, DataFlow::Node rhs) {
exists(TypeTrackerSpecific::TypeTrackerContent c |
TypeTrackerSpecific::basicStoreStep(rhs, node, c) and
lbl = Label::content(c.getAStoreContent())
)
}
pragma[nomagic]
private predicate isUse(DataFlow::Node nd) {
useRoot(_, nd)
@@ -539,27 +580,12 @@ module API {
/** Gets a node reachable from a use-node. */
private DataFlow::LocalSourceNode useCandFwd() { result = useCandFwd(TypeTracker::end()) }
private DataFlow::Node useCandRev(TypeBackTracker tb) {
result = useCandFwd() and
tb.start()
or
exists(TypeBackTracker tb2, DataFlow::LocalSourceNode mid, TypeTracker t |
mid = useCandRev(tb2) and
result = mid.backtrack(tb2, tb) and
pragma[only_bind_out](result) = useCandFwd(t) and
pragma[only_bind_out](t) = pragma[only_bind_out](tb).getACompatibleTypeTracker()
)
}
private DataFlow::LocalSourceNode useCandRev() {
result = useCandRev(TypeBackTracker::end()) and
isUse(result)
}
private predicate isDef(DataFlow::Node rhs) {
// If a call node is relevant as a use-node, treat its arguments as def-nodes
argumentStep(_, useCandFwd(), rhs)
or
defStep(_, trackDefNode(_), rhs)
or
rhs = any(EntryPoint entry).getASink()
}
@@ -608,26 +634,12 @@ module API {
*
* The flow from `src` to the returned node may be inter-procedural.
*/
private DataFlow::Node trackUseNode(DataFlow::LocalSourceNode src, TypeTracker t) {
private DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src, TypeTracker t) {
result = src and
result = useCandRev() and
isUse(src) and
t.start()
or
exists(TypeTracker t2, DataFlow::LocalSourceNode mid |
mid = trackUseNode(src, t2) and
result = useNodeStep(mid, t2, t)
)
}
pragma[nomagic]
private DataFlow::Node useNodeStep(
DataFlow::LocalSourceNode mid, TypeTracker tmid, TypeTracker t
) {
exists(TypeBackTracker tb |
result = mid.track(tmid, t) and
pragma[only_bind_into](result) = useCandRev(pragma[only_bind_into](tb)) and
pragma[only_bind_out](t) = pragma[only_bind_into](tb).getACompatibleTypeTracker()
)
exists(TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t))
}
/**
@@ -682,6 +694,12 @@ module API {
)
)
or
exists(DataFlow::Node predNode, DataFlow::Node succNode |
def(pred, predNode) and
def(succ, succNode) and
defStep(lbl, trackDefNode(predNode), succNode)
)
or
// `pred` is a use of class A
// `succ` is a use of class B
// there exists a class declaration B < A
@@ -754,7 +772,8 @@ module API {
any(DataFlowDispatch::ParameterPosition c).isPositional(n)
} or
MkLabelBlockParameter() or
MkLabelEntryPoint(EntryPoint name)
MkLabelEntryPoint(EntryPoint name) or
MkLabelContent(DataFlow::Content content)
}
/** Provides classes modeling the various edges (labels) in the API graph. */
@@ -844,6 +863,20 @@ module API {
/** Gets the name of the entry point. */
API::EntryPoint getName() { result = name }
}
/** A label representing contents of an object. */
class LabelContent extends ApiLabel, MkLabelContent {
private DataFlow::Content content;
LabelContent() { this = MkLabelContent(content) }
override string toString() {
result = "getContent(" + content.toString().replaceAll(" ", "_") + ")"
}
/** Gets the content represented by this label. */
DataFlow::Content getContent() { result = content }
}
}
/** Gets the `member` edge label for member `m`. */
@@ -870,6 +903,9 @@ module API {
/** Gets the label for the edge from the root node to a custom entry point of the given name. */
LabelEntryPoint entryPoint(API::EntryPoint name) { result.getName() = name }
/** Gets a label representing the given content. */
LabelContent content(DataFlow::Content content) { result.getContent() = content }
/** Gets the API graph label corresponding to the given argument position. */
Label::ApiLabel getLabelFromArgumentPosition(DataFlowDispatch::ArgumentPosition pos) {
exists(int n |

View File

@@ -150,7 +150,7 @@ abstract class SimpleSummarizedCallable extends SummarizedCallable {
bindingset[this]
SimpleSummarizedCallable() { mc.getMethodName() = this }
final override MethodCall getACall() { result = mc }
final override MethodCall getACallSimple() { result = mc }
}
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;

View File

@@ -49,7 +49,10 @@ abstract class LibraryCallable extends string {
LibraryCallable() { any() }
/** Gets a call to this library callable. */
abstract Call getACall();
Call getACall() { none() }
/** Same as `getACall()` except this does not depend on the call graph or API graph. */
Call getACallSimple() { none() }
}
/**
@@ -287,7 +290,7 @@ private DataFlowCallable viableSourceCallable(DataFlowCall call) {
private DataFlowCallable viableLibraryCallable(DataFlowCall call) {
exists(LibraryCallable callable |
result = TLibraryCallable(callable) and
call.asCall().getExpr() = callable.getACall()
call.asCall().getExpr() = [callable.getACall(), callable.getACallSimple()]
)
}

View File

@@ -373,18 +373,24 @@ private module Cached {
n instanceof SynthReturnNode
or
// Needed for stores in type tracking
TypeTrackerSpecific::basicStoreStep(_, n, _)
TypeTrackerSpecific::postUpdateStoreStep(_, n, _)
}
cached
newtype TContentSet =
newtype TOptionalContentSet =
TSingletonContent(Content c) or
TAnyElementContent() or
TKnownOrUnknownElementContent(Content::KnownElementContent c) or
TElementLowerBoundContent(int lower, boolean includeUnknown) {
FlowSummaryImplSpecific::ParsePositions::isParsedElementLowerBoundPosition(_, includeUnknown,
lower)
}
} or
TNoContentSet() // Only used by type-tracking
cached
class TContentSet =
TSingletonContent or TAnyElementContent or TKnownOrUnknownElementContent or
TElementLowerBoundContent;
cached
newtype TContent =
@@ -410,7 +416,9 @@ private module Cached {
|
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
)
}
} or
// Only used by type-tracking
TAttributeName(string name) { name = any(SetterMethodCall c).getTargetName() }
/**
* Holds if `e` is an `ExprNode` that may be returned by a call to `c`.

View File

@@ -314,6 +314,26 @@ module Content {
class UnknownPairValueContent extends PairValueContent, TUnknownPairValueContent {
override string toString() { result = "pair" }
}
/**
* A value stored behind a getter/setter pair.
*
* This is used (only) by type-tracking, as a heuristic since getter/setter pairs tend to operate
* on similar types of objects (i.e. the type flowing into a setter will likely flow out of the getter).
*/
class AttributeNameContent extends Content, TAttributeName {
private string name;
AttributeNameContent() { this = TAttributeName(name) }
override string toString() { result = "attribute " + name }
/** Gets the attribute name. */
string getName() { result = name }
}
/** Gets `AttributeNameContent` of the given name. */
AttributeNameContent getAttributeName(string name) { result.getName() = name }
}
/**

View File

@@ -246,24 +246,15 @@ module ParsePositions {
private import FlowSummaryImpl
private predicate isParamBody(string body) {
exists(AccessPathToken tok |
tok.getName() = "Parameter" and
body = tok.getAnArgument()
)
body = any(AccessPathToken tok).getAnArgument("Parameter")
}
private predicate isArgBody(string body) {
exists(AccessPathToken tok |
tok.getName() = "Argument" and
body = tok.getAnArgument()
)
body = any(AccessPathToken tok).getAnArgument("Argument")
}
private predicate isElementBody(string body) {
exists(AccessPathToken tok |
tok.getName() = "Element" and
body = tok.getAnArgument()
)
body = any(AccessPathToken tok).getAnArgument(["Element", "WithElement", "WithoutElement"])
}
predicate isParsedParameterPosition(string c, int i) {

View File

@@ -3,6 +3,7 @@
private import codeql.ruby.AST
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.ast.internal.Module
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.internal.DataFlowDispatch
@@ -17,6 +18,15 @@ private string lastBlockParam(MethodCall mc, string name, int lastBlockParam) {
lastBlockParam = mc.getBlock().getNumberOfParameters() - 1
}
/**
* Gets a call to the method `name` invoked on the `Array` object
* (not on an array instance).
*/
private MethodCall getAStaticArrayCall(string name) {
result.getMethodName() = name and
resolveConstantReadAccess(result.getReceiver()) = TResolved("Array")
}
/**
* Provides flow summaries for the `Array` class.
*
@@ -34,9 +44,7 @@ module Array {
private class ArrayLiteralSummary extends SummarizedCallable {
ArrayLiteralSummary() { this = "Array.[]" }
override MethodCall getACall() {
result = API::getTopLevelMember("Array").getAMethodCall("[]").getExprNode().getExpr()
}
override MethodCall getACallSimple() { result = getAStaticArrayCall("[]") }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
exists(ArrayIndex i |
@@ -50,9 +58,7 @@ module Array {
private class NewSummary extends SummarizedCallable {
NewSummary() { this = "Array.new" }
override MethodCall getACall() {
result = API::getTopLevelMember("Array").getAnInstantiation().getExprNode().getExpr()
}
override MethodCall getACallSimple() { result = getAStaticArrayCall("new") }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
@@ -72,9 +78,7 @@ module Array {
private class TryConvertSummary extends SummarizedCallable {
TryConvertSummary() { this = "Array.try_convert" }
override MethodCall getACall() {
result = API::getTopLevelMember("Array").getAMethodCall("try_convert").getExprNode().getExpr()
}
override MethodCall getACallSimple() { result = getAStaticArrayCall("try_convert") }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0].WithElement[any]" and
@@ -86,7 +90,7 @@ module Array {
private class SetIntersectionSummary extends SummarizedCallable {
SetIntersectionSummary() { this = "&" }
override BitwiseAndExpr getACall() { any() }
override BitwiseAndExpr getACallSimple() { any() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = ["Argument[self].Element[any]", "Argument[0].Element[any]"] and
@@ -98,7 +102,7 @@ module Array {
private class SetUnionSummary extends SummarizedCallable {
SetUnionSummary() { this = "|" }
override BitwiseOrExpr getACall() { any() }
override BitwiseOrExpr getACallSimple() { any() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = ["Argument[self].Element[any]", "Argument[0].Element[any]"] and
@@ -110,7 +114,7 @@ module Array {
private class RepetitionSummary extends SummarizedCallable {
RepetitionSummary() { this = "*" }
override MulExpr getACall() { any() }
override MulExpr getACallSimple() { any() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
@@ -122,7 +126,7 @@ module Array {
private class ConcatenationSummary extends SummarizedCallable {
ConcatenationSummary() { this = "+" }
override AddExpr getACall() { any() }
override AddExpr getACallSimple() { any() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
@@ -139,7 +143,7 @@ module Array {
private class SetDifferenceSummary extends SummarizedCallable {
SetDifferenceSummary() { this = "-" }
override SubExpr getACall() { any() }
override SubExpr getACallSimple() { any() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[self].Element[any]" and
@@ -152,7 +156,7 @@ module Array {
private class AppendOperatorSummary extends SummarizedCallable {
AppendOperatorSummary() { this = "<<" }
override LShiftExpr getACall() { any() }
override LShiftExpr getACallSimple() { any() }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
(
@@ -176,9 +180,11 @@ module Array {
ElementReferenceReadMethodName methodName; // adding this as a field helps give a better join order
bindingset[this]
ElementReferenceReadSummary() { mc.getMethodName() = methodName }
ElementReferenceReadSummary() {
mc.getMethodName() = methodName and not mc = getAStaticArrayCall(methodName)
}
override MethodCall getACall() { result = mc }
override MethodCall getACallSimple() { result = mc }
}
/** A call to `[]` with a known index. */
@@ -303,7 +309,7 @@ module Array {
bindingset[this]
ElementReferenceStoreSummary() { mc.getMethodName() = "[]=" }
final override MethodCall getACall() { result = mc }
final override MethodCall getACallSimple() { result = mc }
}
/** A call to `[]=` with a known index. */

View File

@@ -5,6 +5,7 @@ private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.ast.internal.Module
/**
* Provides flow summaries for the `Hash` class.
@@ -35,12 +36,19 @@ module Hash {
)
}
/**
* Gets a call to the method `name` invoked on the `Hash` object
* (not on a hash instance).
*/
private MethodCall getAStaticHashCall(string name) {
result.getMethodName() = name and
resolveConstantReadAccess(result.getReceiver()) = TResolved("Hash")
}
private class HashLiteralSummary extends SummarizedCallable {
HashLiteralSummary() { this = "Hash.[]" }
final override MethodCall getACall() {
result = API::getTopLevelMember("Hash").getAMethodCall("[]").getExprNode().getExpr()
}
final override MethodCall getACallSimple() { result = getAStaticHashCall("[]") }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
// { 'nonsymbol' => x }
@@ -77,9 +85,8 @@ module Hash {
private class HashNewSummary extends SummarizedCallable {
HashNewSummary() { this = "Hash[]" }
final override ElementReference getACall() {
result.getReceiver() =
API::getTopLevelMember("Hash").getAValueReachableFromSource().asExpr().getExpr() and
final override MethodCall getACallSimple() {
result = getAStaticHashCall("[]") and
result.getNumberOfArguments() = 1
}
@@ -117,9 +124,8 @@ module Hash {
)
}
final override ElementReference getACall() {
result.getReceiver() =
API::getTopLevelMember("Hash").getAValueReachableFromSource().asExpr().getExpr() and
final override MethodCall getACallSimple() {
result = getAStaticHashCall("[]") and
key = result.getArgument(i - 1).getConstantValue() and
exists(result.getArgument(i))
}
@@ -135,9 +141,7 @@ module Hash {
private class TryConvertSummary extends SummarizedCallable {
TryConvertSummary() { this = "Hash.try_convert" }
override MethodCall getACall() {
result = API::getTopLevelMember("Hash").getAMethodCall("try_convert").getExprNode().getExpr()
}
override MethodCall getACallSimple() { result = getAStaticHashCall("try_convert") }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0].WithElement[any]" and
@@ -152,7 +156,7 @@ module Hash {
bindingset[this]
StoreSummary() { mc.getMethodName() = "store" and mc.getNumberOfArguments() = 2 }
final override MethodCall getACall() { result = mc }
final override MethodCall getACallSimple() { result = mc }
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[1]" and

View File

@@ -31,6 +31,7 @@ import codeql.ruby.dataflow.internal.AccessPathSyntax as AccessPathSyntax
import codeql.ruby.DataFlow::DataFlow as DataFlow
private import AccessPathSyntax
private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
private import codeql.ruby.dataflow.internal.FlowSummaryImpl::Public
private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
/**
@@ -118,9 +119,11 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) {
result =
node.getASuccessor(API::Label::getLabelFromParameterPosition(FlowSummaryImplSpecific::parseArgBody(token
.getAnArgument())))
// Note: The "Element" token is not implemented yet, as it ultimately requires type-tracking and
// API graphs to be aware of the steps involving Element contributed by the standard library model.
// Type-tracking cannot summarize function calls on its own, so it doesn't benefit from synthesized callables.
or
exists(DataFlow::ContentSet contents |
SummaryComponent::content(contents) = FlowSummaryImplSpecific::interpretComponentSpecific(token) and
result = node.getContents(contents)
)
}
/**
@@ -160,7 +163,7 @@ InvokeNode getAnInvocationOf(API::Node node) { result = node }
*/
bindingset[name]
predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
name = ["Member", "Method", "Instance", "WithBlock", "WithoutBlock"]
name = ["Member", "Method", "Instance", "WithBlock", "WithoutBlock", "Element", "Field"]
}
/**
@@ -177,7 +180,7 @@ predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) {
*/
bindingset[name, argument]
predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string argument) {
name = ["Member", "Method"] and
name = ["Member", "Method", "Element", "Field"] and
exists(argument)
or
name = ["Argument", "Parameter"] and

View File

@@ -2,27 +2,6 @@
private import TypeTrackerSpecific
/**
* A string that may appear as the name of a piece of content. This will usually include things like:
* - Attribute names (in Python)
* - Property names (in JavaScript)
*
* In general, this can also be used to model things like stores to specific list indices. To ensure
* correctness, it is important that
*
* - different types of content do not have overlapping names, and
* - the empty string `""` is not a valid piece of content, as it is used to indicate the absence of
* content instead.
*/
class ContentName extends string {
ContentName() { this = getPossibleContentName() }
}
/** A content name, or the empty string (representing no content). */
class OptionalContentName extends string {
OptionalContentName() { this instanceof ContentName or this = "" }
}
cached
private module Cached {
/**
@@ -33,48 +12,78 @@ private module Cached {
LevelStep() or
CallStep() or
ReturnStep() or
StoreStep(ContentName content) or
LoadStep(ContentName content) or
StoreStep(TypeTrackerContent content) { basicStoreStep(_, _, content) } or
LoadStep(TypeTrackerContent content) { basicLoadStep(_, _, content) } or
JumpStep()
pragma[nomagic]
private TypeTracker noContentTypeTracker(boolean hasCall) {
result = MkTypeTracker(hasCall, noContent())
}
/** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */
cached
TypeTracker append(TypeTracker tt, StepSummary step) {
exists(Boolean hasCall, OptionalContentName content | tt = MkTypeTracker(hasCall, content) |
exists(Boolean hasCall, OptionalTypeTrackerContent currentContents |
tt = MkTypeTracker(hasCall, currentContents)
|
step = LevelStep() and result = tt
or
step = CallStep() and result = MkTypeTracker(true, content)
step = CallStep() and result = MkTypeTracker(true, currentContents)
or
step = ReturnStep() and hasCall = false and result = tt
or
step = LoadStep(content) and result = MkTypeTracker(hasCall, "")
or
exists(string p | step = StoreStep(p) and content = "" and result = MkTypeTracker(hasCall, p))
or
step = JumpStep() and
result = MkTypeTracker(false, content)
result = MkTypeTracker(false, currentContents)
)
or
exists(TypeTrackerContent storeContents, boolean hasCall |
exists(TypeTrackerContent loadContents |
step = LoadStep(pragma[only_bind_into](loadContents)) and
tt = MkTypeTracker(hasCall, storeContents) and
compatibleContents(storeContents, loadContents) and
result = noContentTypeTracker(hasCall)
)
or
step = StoreStep(pragma[only_bind_into](storeContents)) and
tt = noContentTypeTracker(hasCall) and
result = MkTypeTracker(hasCall, storeContents)
)
}
pragma[nomagic]
private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) {
result = MkTypeBackTracker(hasReturn, noContent())
}
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
cached
TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) {
exists(Boolean hasReturn, string content | tbt = MkTypeBackTracker(hasReturn, content) |
exists(Boolean hasReturn, OptionalTypeTrackerContent content |
tbt = MkTypeBackTracker(hasReturn, content)
|
step = LevelStep() and result = tbt
or
step = CallStep() and hasReturn = false and result = tbt
or
step = ReturnStep() and result = MkTypeBackTracker(true, content)
or
exists(string p |
step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p)
)
or
step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
or
step = JumpStep() and
result = MkTypeBackTracker(false, content)
)
or
exists(TypeTrackerContent loadContents, boolean hasReturn |
exists(TypeTrackerContent storeContents |
step = StoreStep(pragma[only_bind_into](storeContents)) and
tbt = MkTypeBackTracker(hasReturn, loadContents) and
compatibleContents(storeContents, loadContents) and
result = noContentTypeBackTracker(hasReturn)
)
or
step = LoadStep(pragma[only_bind_into](loadContents)) and
tbt = noContentTypeBackTracker(hasReturn) and
result = MkTypeBackTracker(hasReturn, loadContents)
)
}
/**
@@ -114,9 +123,9 @@ class StepSummary extends TStepSummary {
or
this instanceof ReturnStep and result = "return"
or
exists(string content | this = StoreStep(content) | result = "store " + content)
exists(TypeTrackerContent content | this = StoreStep(content) | result = "store " + content)
or
exists(string content | this = LoadStep(content) | result = "load " + content)
exists(TypeTrackerContent content | this = LoadStep(content) | result = "load " + content)
or
this instanceof JumpStep and result = "jump"
}
@@ -130,7 +139,7 @@ private predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSu
levelStep(nodeFrom, nodeTo) and
summary = LevelStep()
or
exists(string content |
exists(TypeTrackerContent content |
StepSummary::localSourceStoreStep(nodeFrom, nodeTo, content) and
summary = StoreStep(content)
or
@@ -180,7 +189,7 @@ module StepSummary {
}
/**
* Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
* Holds if `nodeFrom` is being written to the `content` of the object in `nodeTo`.
*
* Note that `nodeTo` will always be a local source node that flows to the place where the content
* is written in `basicStoreStep`. This may lead to the flow of information going "back in time"
@@ -204,12 +213,23 @@ module StepSummary {
* function. This means we will track the fact that `x.attr` can have the type of `y` into the
* assignment to `z` inside `bar`, even though this attribute write happens _after_ `bar` is called.
*/
predicate localSourceStoreStep(Node nodeFrom, TypeTrackingNode nodeTo, string content) {
predicate localSourceStoreStep(Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContent content) {
exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content))
}
}
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalContentName content)
private newtype TTypeTracker =
MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) {
content = noContent()
or
// Restrict `content` to those that might eventually match a load.
// We can't rely on `basicStoreStep` since `startInContent` might be used with
// a content that has no corresponding store.
exists(TypeTrackerContent loadContents |
basicLoadStep(_, _, loadContents) and
compatibleContents(content, loadContents)
)
}
/**
* A summary of the steps needed to track a value to a given dataflow node.
@@ -240,7 +260,7 @@ private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalContentNam
*/
class TypeTracker extends TTypeTracker {
Boolean hasCall;
OptionalContentName content;
OptionalTypeTrackerContent content;
TypeTracker() { this = MkTypeTracker(hasCall, content) }
@@ -251,7 +271,11 @@ class TypeTracker extends TTypeTracker {
string toString() {
exists(string withCall, string withContent |
(if hasCall = true then withCall = "with" else withCall = "without") and
(if content != "" then withContent = " with content " + content else withContent = "") and
(
if content != noContent()
then withContent = " with content " + content
else withContent = ""
) and
result = "type tracker " + withCall + " call steps" + withContent
)
}
@@ -259,24 +283,26 @@ class TypeTracker extends TTypeTracker {
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() { hasCall = false and content = "" }
predicate start() { hasCall = false and content = noContent() }
/**
* Holds if this is the starting point of type tracking, and the value starts in the content named `contentName`.
* The type tracking only ends after the content has been loaded.
*/
predicate startInContent(ContentName contentName) { hasCall = false and content = contentName }
predicate startInContent(TypeTrackerContent contentName) {
hasCall = false and content = contentName
}
/**
* Holds if this is the starting point of type tracking
* when tracking a parameter into a call, but not out of it.
*/
predicate call() { hasCall = true and content = "" }
predicate call() { hasCall = true and content = noContent() }
/**
* Holds if this is the end point of type tracking.
*/
predicate end() { content = "" }
predicate end() { content = noContent() }
/**
* INTERNAL. DO NOT USE.
@@ -290,7 +316,7 @@ class TypeTracker extends TTypeTracker {
*
* Gets the content associated with this type tracker.
*/
string getContent() { result = content }
OptionalTypeTrackerContent getContent() { result = content }
/**
* Gets a type tracker that starts where this one has left off to allow continued
@@ -298,7 +324,7 @@ class TypeTracker extends TTypeTracker {
*
* This predicate is only defined if the type is not associated to a piece of content.
*/
TypeTracker continue() { content = "" and result = this }
TypeTracker continue() { content = noContent() and result = this }
/**
* Gets the summary that corresponds to having taken a forwards
@@ -356,7 +382,16 @@ module TypeTracker {
TypeTracker end() { result.end() }
}
private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, OptionalContentName content)
private newtype TTypeBackTracker =
MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) {
content = noContent()
or
// As in MkTypeTracker, restrict `content` to those that might eventually match a store.
exists(TypeTrackerContent storeContent |
basicStoreStep(_, _, storeContent) and
compatibleContents(storeContent, content)
)
}
/**
* A summary of the steps needed to back-track a use of a value to a given dataflow node.
@@ -390,7 +425,7 @@ private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, Optional
*/
class TypeBackTracker extends TTypeBackTracker {
Boolean hasReturn;
string content;
OptionalTypeTrackerContent content;
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
@@ -401,7 +436,11 @@ class TypeBackTracker extends TTypeBackTracker {
string toString() {
exists(string withReturn, string withContent |
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
(if content != "" then withContent = " with content " + content else withContent = "") and
(
if content != noContent()
then withContent = " with content " + content
else withContent = ""
) and
result = "type back-tracker " + withReturn + " return steps" + withContent
)
}
@@ -409,12 +448,12 @@ class TypeBackTracker extends TTypeBackTracker {
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() { hasReturn = false and content = "" }
predicate start() { hasReturn = false and content = noContent() }
/**
* Holds if this is the end point of type tracking.
*/
predicate end() { content = "" }
predicate end() { content = noContent() }
/**
* INTERNAL. DO NOT USE.
@@ -429,7 +468,7 @@ class TypeBackTracker extends TTypeBackTracker {
*
* This predicate is only defined if the type has not been tracked into a piece of content.
*/
TypeBackTracker continue() { content = "" and result = this }
TypeBackTracker continue() { content = noContent() and result = this }
/**
* Gets the summary that corresponds to having taken a backwards

View File

@@ -1,16 +1,46 @@
private import codeql.ruby.AST as Ast
private import codeql.ruby.CFG as Cfg
private import Cfg::CfgNodes
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon
private import codeql.ruby.dataflow.internal.DataFlowPublic as DataFlowPublic
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
private import codeql.ruby.dataflow.internal.SsaImpl as SsaImpl
private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
private import codeql.ruby.dataflow.internal.AccessPathSyntax
class Node = DataFlowPublic::Node;
class TypeTrackingNode = DataFlowPublic::LocalSourceNode;
class TypeTrackerContent = DataFlowPublic::ContentSet;
/**
* An optional content set, that is, a `ContentSet` or the special "no content set" value.
*/
class OptionalTypeTrackerContent extends DataFlowPrivate::TOptionalContentSet {
/** Gets a textual representation of this content set. */
string toString() {
this instanceof DataFlowPrivate::TNoContentSet and
result = "no content"
or
result = this.(DataFlowPublic::ContentSet).toString()
}
}
/**
* Holds if a value stored with `storeContents` can be read back with `loadContents`.
*/
pragma[inline]
predicate compatibleContents(TypeTrackerContent storeContents, TypeTrackerContent loadContents) {
storeContents.getAStoreContent() = loadContents.getAReadContent()
}
/** Gets the "no content set" value to use for a type tracker not inside any content. */
OptionalTypeTrackerContent noContent() { result = DataFlowPrivate::TNoContentSet() }
/** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */
predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
@@ -37,14 +67,6 @@ private predicate summarizedLocalStep(Node nodeFrom, Node nodeTo) {
/** Holds if there is a level step from `nodeFrom` to `nodeTo`. */
predicate levelStep(Node nodeFrom, Node nodeTo) { summarizedLocalStep(nodeFrom, nodeTo) }
/**
* Gets the name of a possible piece of content. This will usually include things like
*
* - Attribute names (in Python)
* - Property names (in JavaScript)
*/
string getPossibleContentName() { result = getSetterCallAttributeName(_) }
pragma[noinline]
private predicate argumentPositionMatch(
ExprNodes::CallCfgNode call, DataFlowPrivate::ArgumentNode arg,
@@ -115,11 +137,11 @@ predicate returnStep(Node nodeFrom, Node nodeTo) {
}
/**
* Holds if `nodeFrom` is being written to the `content` content of the object
* Holds if `nodeFrom` is being written to the `contents` of the object
* in `nodeTo`.
*
* Note that the choice of `nodeTo` does not have to make sense
* "chronologically". All we care about is whether the `content` content of
* "chronologically". All we care about is whether the `contents` of
* `nodeTo` can have a specific type, and the assumption is that if a specific
* type appears here, then any access of that particular content can yield
* something of that particular type.
@@ -138,17 +160,38 @@ predicate returnStep(Node nodeFrom, Node nodeTo) {
* z = x.content
* end
* ```
* for the content write `x.content = y`, we will have `content` being the
* for the content write `x.content = y`, we will have `contents` being the
* literal string `"content"`, `nodeFrom` will be `y`, and `nodeTo` will be the
* `Foo` object created on the first line of the function. This means we will
* track the fact that `x.content` can have the type of `y` into the assignment
* to `z` inside `bar`, even though this content write happens _after_ `bar` is
* called.
*/
predicate basicStoreStep(Node nodeFrom, Node nodeTo, string content) {
predicate basicStoreStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) {
postUpdateStoreStep(nodeFrom, nodeTo, contents)
or
exists(
SummarizedCallable callable, DataFlowPublic::CallNode call, SummaryComponent input,
SummaryComponent output
|
hasStoreSummary(callable, contents, input, output) and
call.asExpr().getExpr() = callable.getACallSimple() and
nodeFrom = evaluateSummaryComponentLocal(call, input) and
nodeTo = evaluateSummaryComponentLocal(call, output)
)
}
/**
* Holds if a store step `nodeFrom -> nodeTo` with `contents` exists, where the destination node
* is a post-update node that should be treated as a local source node.
*/
predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) {
// TODO: support SetterMethodCall inside TuplePattern
exists(ExprNodes::MethodCallCfgNode call |
content = getSetterCallAttributeName(call.getExpr()) and
contents
.isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr()
.(Ast::SetterMethodCall)
.getTargetName())) and
nodeTo.(DataFlowPublic::PostUpdateNode).getPreUpdateNode().asExpr() = call.getReceiver() and
call.getExpr() instanceof Ast::SetterMethodCall and
call.getArgument(call.getNumberOfArguments() - 1) =
@@ -156,32 +199,26 @@ predicate basicStoreStep(Node nodeFrom, Node nodeTo, string content) {
)
}
/**
* Returns the name of the attribute being set by the setter method call, i.e.
* the name of the setter method without the trailing `=`. In the following
* example, the result is `"bar"`.
*
* ```rb
* foo.bar = 1
* ```
*/
private string getSetterCallAttributeName(Ast::SetterMethodCall call) {
// TODO: this should be exposed in `SetterMethodCall`
exists(string setterName |
setterName = call.getMethodName() and result = setterName.prefix(setterName.length() - 1)
)
}
/**
* Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`.
*/
predicate basicLoadStep(Node nodeFrom, Node nodeTo, string content) {
predicate basicLoadStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) {
exists(ExprNodes::MethodCallCfgNode call |
call.getExpr().getNumberOfArguments() = 0 and
content = call.getExpr().getMethodName() and
contents.isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr().getMethodName())) and
nodeFrom.asExpr() = call.getReceiver() and
nodeTo.asExpr() = call
)
or
exists(
SummarizedCallable callable, DataFlowPublic::CallNode call, SummaryComponent input,
SummaryComponent output
|
hasLoadSummary(callable, contents, input, output) and
call.asExpr().getExpr() = callable.getACallSimple() and
nodeFrom = evaluateSummaryComponentLocal(call, input) and
nodeTo = evaluateSummaryComponentLocal(call, output)
)
}
/**
@@ -190,3 +227,40 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, string content) {
class Boolean extends boolean {
Boolean() { this = true or this = false }
}
private import SummaryComponentStack
private predicate hasStoreSummary(
SummarizedCallable callable, DataFlow::ContentSet contents, SummaryComponent input,
SummaryComponent output
) {
callable
.propagatesFlow(singleton(input),
push(SummaryComponent::content(contents), singleton(output)), true)
}
private predicate hasLoadSummary(
SummarizedCallable callable, DataFlow::ContentSet contents, SummaryComponent input,
SummaryComponent output
) {
callable
.propagatesFlow(push(SummaryComponent::content(contents), singleton(input)),
singleton(output), true)
}
/**
* Gets a data flow node corresponding an argument or return value of `call`,
* as specified by `component`.
*/
bindingset[call, component]
private DataFlowPublic::Node evaluateSummaryComponentLocal(
DataFlowPublic::CallNode call, SummaryComponent component
) {
exists(DataFlowDispatch::ParameterPosition pos |
component = SummaryComponent::argument(pos) and
argumentPositionMatch(call.asExpr(), result, pos)
)
or
component = SummaryComponent::return() and
result = call
}

View File

@@ -4,3 +4,5 @@ classMethodCalls
instanceMethodCalls
| test1.rb:61:1:61:12 | Use getMember("M1").getMember("C1").getMethod("new").getReturn().getMethod("m").getReturn() |
| test1.rb:62:1:62:12 | Use getMember("M2").getMember("C3").getMethod("new").getReturn().getMethod("m").getReturn() |
flowThroughArray
| test1.rb:73:1:73:10 | call to m |

View File

@@ -2,6 +2,7 @@
* Tests of the public API of API Graphs
*/
import ruby
import codeql.ruby.ApiGraphs
query predicate classMethodCalls(API::Node node) {
@@ -11,3 +12,8 @@ query predicate classMethodCalls(API::Node node) {
query predicate instanceMethodCalls(API::Node node) {
node = API::getTopLevelMember("M1").getMember("C1").getInstance().getReturn("m")
}
query predicate flowThroughArray(DataFlow::Node node) {
node =
API::getTopLevelMember("A").getMember("B").getMember("C").getMethod("m").getReturn().asSource()
}

View File

@@ -68,3 +68,8 @@ def userDefinedFunction(x, y)
x.customEntryPointCall(y) #$ call=entryPoint("CustomEntryPointCall") use=entryPoint("CustomEntryPointCall").getReturn() rhs=entryPoint("CustomEntryPointCall").getParameter(0)
x.customEntryPointUse(y) #$ use=entryPoint("CustomEntryPointUse")
end
array = [A::B::C] #$ use=getMember("Array").getMethod("[]").getReturn()
array[0].m #$ use=getMember("A").getMember("B").getMember("C").getMethod("m").getReturn()
A::B::C[0] #$ use=getMember("A").getMember("B").getMember("C").getContent(element_0)

View File

@@ -38,6 +38,12 @@ edges
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:133:19:133:25 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:134:19:134:25 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:134:19:134:25 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:138:26:138:32 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:138:26:138:32 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:140:16:140:22 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:140:16:140:22 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:143:39:143:45 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:143:39:143:45 | tainted |
| summaries.rb:1:20:1:36 | call to source : | summaries.rb:1:11:1:36 | call to identity : |
| summaries.rb:1:20:1:36 | call to source : | summaries.rb:1:11:1:36 | call to identity : |
| summaries.rb:4:12:7:3 | call to apply_block : | summaries.rb:9:6:9:13 | tainted2 |
@@ -192,6 +198,9 @@ edges
| summaries.rb:115:16:115:22 | [post] tainted : | summaries.rb:130:23:130:29 | tainted |
| summaries.rb:115:16:115:22 | [post] tainted : | summaries.rb:133:19:133:25 | tainted |
| summaries.rb:115:16:115:22 | [post] tainted : | summaries.rb:134:19:134:25 | tainted |
| summaries.rb:115:16:115:22 | [post] tainted : | summaries.rb:138:26:138:32 | tainted |
| summaries.rb:115:16:115:22 | [post] tainted : | summaries.rb:140:16:140:22 | tainted |
| summaries.rb:115:16:115:22 | [post] tainted : | summaries.rb:143:39:143:45 | tainted |
| summaries.rb:115:16:115:22 | tainted : | summaries.rb:115:16:115:22 | [post] tainted : |
| summaries.rb:115:16:115:22 | tainted : | summaries.rb:115:25:115:25 | [post] y : |
| summaries.rb:115:16:115:22 | tainted : | summaries.rb:115:33:115:33 | [post] z : |
@@ -397,6 +406,12 @@ nodes
| summaries.rb:133:19:133:25 | tainted | semmle.label | tainted |
| summaries.rb:134:19:134:25 | tainted | semmle.label | tainted |
| summaries.rb:134:19:134:25 | tainted | semmle.label | tainted |
| summaries.rb:138:26:138:32 | tainted | semmle.label | tainted |
| summaries.rb:138:26:138:32 | tainted | semmle.label | tainted |
| summaries.rb:140:16:140:22 | tainted | semmle.label | tainted |
| summaries.rb:140:16:140:22 | tainted | semmle.label | tainted |
| summaries.rb:143:39:143:45 | tainted | semmle.label | tainted |
| summaries.rb:143:39:143:45 | tainted | semmle.label | tainted |
subpaths
invalidSpecComponent
#select
@@ -488,6 +503,12 @@ invalidSpecComponent
| summaries.rb:133:19:133:25 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:133:19:133:25 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:134:19:134:25 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:134:19:134:25 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:134:19:134:25 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:134:19:134:25 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:138:26:138:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:138:26:138:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:138:26:138:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:138:26:138:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:140:16:140:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:140:16:140:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:140:16:140:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:140:16:140:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:143:39:143:45 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:143:39:143:45 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:143:39:143:45 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:143:39:143: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 |

View File

@@ -143,6 +143,9 @@ private class SinkFromModel extends ModelInput::SinkModelCsv {
"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", //
]
}
}

View File

@@ -134,3 +134,12 @@ Alias::Foo.method(tainted) # $ hasValueFlow=tainted
Alias::Bar.method(tainted) # $ hasValueFlow=tainted
Something::Foo.method(tainted)
Alias::Something.method(tainted)
Foo.getSinks()[0].mySink(tainted) # $ hasValueFlow=tainted
Foo.arraySink(tainted)
Foo.arraySink([tainted]) # $ hasValueFlow=tainted
Foo.secondArrayElementIsSink([tainted, "safe", "safe"])
Foo.secondArrayElementIsSink(["safe", tainted, "safe"]) # $ hasValueFlow=tainted
Foo.secondArrayElementIsSink(["safe", "safe", tainted])
Foo.secondArrayElementIsSink([tainted] * 10) # $ MISSING: hasValueFlow=tainted

View File

@@ -55,12 +55,12 @@ track
| type_tracker.rb:14:5:14:13 | call to field= | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content field | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content field | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:17:14:23 | "hello" |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps with content field | type_tracker.rb:14:5:14:7 | [post] var |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps with content attribute field | type_tracker.rb:14:5:14:7 | [post] var |
| type_tracker.rb:14:17:14:23 | __synth__0 | type tracker without call steps | type_tracker.rb:14:17:14:23 | __synth__0 |
| type_tracker.rb:15:5:15:18 | call to puts | type tracker without call steps | type_tracker.rb:12:1:16:3 | return return in m |
| type_tracker.rb:15:5:15:18 | call to puts | type tracker without call steps | type_tracker.rb:15:5:15:18 | call to puts |
@@ -147,6 +147,147 @@ track
| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type tracker without call steps | type_tracker.rb:32:26:32:26 | 8 |
| type_tracker.rb:34:1:53:3 | &block | type tracker without call steps | type_tracker.rb:34:1:53:3 | &block |
| type_tracker.rb:34:1:53:3 | return return in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:1:53:3 | self in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | self in throughArray |
| type_tracker.rb:34:1:53:3 | throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | throughArray |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:34:23:34:23 | y | type tracker with call steps | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:26:34:26 | z | type tracker with call steps | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:35:5:35:7 | tmp | type tracker without call steps | type_tracker.rb:35:5:35:7 | tmp |
| type_tracker.rb:35:11:35:15 | Array | type tracker without call steps | type_tracker.rb:35:11:35:15 | Array |
| type_tracker.rb:35:11:35:15 | call to [] | type tracker without call steps | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:36:5:36:10 | ...[...] | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:36:9:36:9 | 0 | type tracker without call steps | type_tracker.rb:36:9:36:9 | 0 |
| type_tracker.rb:38:5:38:9 | array | type tracker without call steps | type_tracker.rb:38:5:38:9 | array |
| type_tracker.rb:38:13:38:25 | Array | type tracker without call steps | type_tracker.rb:38:13:38:25 | Array |
| type_tracker.rb:38:13:38:25 | call to [] | type tracker without call steps | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:38:14:38:14 | 1 | type tracker without call steps | type_tracker.rb:38:14:38:14 | 1 |
| type_tracker.rb:38:14:38:14 | 1 | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:38:14:38:14 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:38:16:38:16 | 2 | type tracker without call steps | type_tracker.rb:38:16:38:16 | 2 |
| type_tracker.rb:38:16:38:16 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:38:18:38:18 | 3 | type tracker without call steps | type_tracker.rb:38:18:38:18 | 3 |
| type_tracker.rb:38:18:38:18 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:38:20:38:20 | 4 | type tracker without call steps | type_tracker.rb:38:20:38:20 | 4 |
| type_tracker.rb:38:20:38:20 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:38:22:38:22 | 5 | type tracker without call steps | type_tracker.rb:38:22:38:22 | 5 |
| type_tracker.rb:38:22:38:22 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:38:24:38:24 | 6 | type tracker without call steps | type_tracker.rb:38:24:38:24 | 6 |
| type_tracker.rb:38:24:38:24 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:39:5:39:9 | [post] array | type tracker without call steps | type_tracker.rb:39:5:39:9 | [post] array |
| type_tracker.rb:39:5:39:12 | call to []= | type tracker without call steps | type_tracker.rb:39:5:39:12 | call to []= |
| type_tracker.rb:39:16:39:18 | __synth__0 | type tracker without call steps | type_tracker.rb:39:16:39:18 | __synth__0 |
| type_tracker.rb:40:5:40:12 | ...[...] | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:40:11:40:11 | 0 | type tracker without call steps | type_tracker.rb:40:11:40:11 | 0 |
| type_tracker.rb:42:5:42:10 | array2 | type tracker without call steps | type_tracker.rb:42:5:42:10 | array2 |
| type_tracker.rb:42:14:42:26 | Array | type tracker without call steps | type_tracker.rb:42:14:42:26 | Array |
| type_tracker.rb:42:14:42:26 | call to [] | type tracker without call steps | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:42:15:42:15 | 1 | type tracker without call steps | type_tracker.rb:42:15:42:15 | 1 |
| type_tracker.rb:42:15:42:15 | 1 | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:15:42:15 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:42:17:42:17 | 2 | type tracker without call steps | type_tracker.rb:42:17:42:17 | 2 |
| type_tracker.rb:42:17:42:17 | 2 | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:17:42:17 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:42:19:42:19 | 3 | type tracker without call steps | type_tracker.rb:42:19:42:19 | 3 |
| type_tracker.rb:42:19:42:19 | 3 | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:19:42:19 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:42:21:42:21 | 4 | type tracker without call steps | type_tracker.rb:42:21:42:21 | 4 |
| type_tracker.rb:42:21:42:21 | 4 | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:21:42:21 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:42:23:42:23 | 5 | type tracker without call steps | type_tracker.rb:42:23:42:23 | 5 |
| type_tracker.rb:42:23:42:23 | 5 | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:23:42:23 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:42:25:42:25 | 6 | type tracker without call steps | type_tracker.rb:42:25:42:25 | 6 |
| type_tracker.rb:42:25:42:25 | 6 | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:25:42:25 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:43:5:43:10 | [post] array2 | type tracker without call steps | type_tracker.rb:43:5:43:10 | [post] array2 |
| type_tracker.rb:43:5:43:13 | call to []= | type tracker without call steps | type_tracker.rb:43:5:43:13 | call to []= |
| type_tracker.rb:43:12:43:12 | 0 | type tracker without call steps | type_tracker.rb:43:12:43:12 | 0 |
| type_tracker.rb:43:17:43:19 | __synth__0 | type tracker without call steps | type_tracker.rb:43:17:43:19 | __synth__0 |
| type_tracker.rb:44:5:44:13 | ...[...] | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:46:5:46:10 | array3 | type tracker without call steps | type_tracker.rb:46:5:46:10 | array3 |
| type_tracker.rb:46:14:46:26 | Array | type tracker without call steps | type_tracker.rb:46:14:46:26 | Array |
| type_tracker.rb:46:14:46:26 | call to [] | type tracker without call steps | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:46:15:46:15 | 1 | type tracker without call steps | type_tracker.rb:46:15:46:15 | 1 |
| type_tracker.rb:46:15:46:15 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:46:17:46:17 | 2 | type tracker without call steps | type_tracker.rb:46:17:46:17 | 2 |
| type_tracker.rb:46:17:46:17 | 2 | type tracker without call steps | type_tracker.rb:48:5:48:13 | ...[...] |
| type_tracker.rb:46:17:46:17 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:46:19:46:19 | 3 | type tracker without call steps | type_tracker.rb:46:19:46:19 | 3 |
| type_tracker.rb:46:19:46:19 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:46:21:46:21 | 4 | type tracker without call steps | type_tracker.rb:46:21:46:21 | 4 |
| type_tracker.rb:46:21:46:21 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:46:23:46:23 | 5 | type tracker without call steps | type_tracker.rb:46:23:46:23 | 5 |
| type_tracker.rb:46:23:46:23 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:46:25:46:25 | 6 | type tracker without call steps | type_tracker.rb:46:25:46:25 | 6 |
| type_tracker.rb:46:25:46:25 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:47:5:47:10 | [post] array3 | type tracker without call steps | type_tracker.rb:47:5:47:10 | [post] array3 |
| type_tracker.rb:47:5:47:13 | call to []= | type tracker without call steps | type_tracker.rb:47:5:47:13 | call to []= |
| type_tracker.rb:47:12:47:12 | 0 | type tracker without call steps | type_tracker.rb:47:12:47:12 | 0 |
| type_tracker.rb:47:17:47:19 | __synth__0 | type tracker without call steps | type_tracker.rb:47:17:47:19 | __synth__0 |
| type_tracker.rb:48:5:48:13 | ...[...] | type tracker without call steps | type_tracker.rb:48:5:48:13 | ...[...] |
| type_tracker.rb:48:12:48:12 | 1 | type tracker without call steps | type_tracker.rb:48:12:48:12 | 1 |
| type_tracker.rb:50:5:50:10 | array4 | type tracker without call steps | type_tracker.rb:50:5:50:10 | array4 |
| type_tracker.rb:50:14:50:26 | Array | type tracker without call steps | type_tracker.rb:50:14:50:26 | Array |
| type_tracker.rb:50:14:50:26 | call to [] | type tracker without call steps | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:50:15:50:15 | 1 |
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:50:17:50:17 | 2 |
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:50:19:50:19 | 3 |
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:50:21:50:21 | 4 |
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:50:23:50:23 | 5 |
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:50:25:50:25 | 6 |
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:51:5:51:10 | [post] array4 | type tracker without call steps | type_tracker.rb:51:5:51:10 | [post] array4 |
| type_tracker.rb:51:5:51:13 | call to []= | type tracker without call steps | type_tracker.rb:51:5:51:13 | call to []= |
| type_tracker.rb:51:17:51:19 | __synth__0 | type tracker without call steps | type_tracker.rb:51:17:51:19 | __synth__0 |
| type_tracker.rb:52:5:52:13 | ...[...] | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:52:5:52:13 | ...[...] | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] |
trackEnd
| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) |
| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:18:1:21:3 | self (positional) |
@@ -358,3 +499,185 @@ trackEnd
| type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:26:10:26:11 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:32:26:32:26 | 8 |
| type_tracker.rb:34:1:53:3 | &block | type_tracker.rb:34:1:53:3 | &block |
| type_tracker.rb:34:1:53:3 | return return in throughArray | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:1:53:3 | self in throughArray | type_tracker.rb:34:1:53:3 | self in throughArray |
| type_tracker.rb:34:1:53:3 | throughArray | type_tracker.rb:34:1:53:3 | throughArray |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:12 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:12 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:18 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:18 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:13 | __synth__0 |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:19 | ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:51:12:51:12 | y |
| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:51:12:51:12 | y |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:52:12:52:12 | z |
| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:52:12:52:12 | z |
| type_tracker.rb:35:5:35:7 | tmp | type_tracker.rb:35:5:35:7 | tmp |
| type_tracker.rb:35:11:35:15 | Array | type_tracker.rb:35:11:35:15 | Array |
| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:5:35:15 | ... = ... |
| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:5:35:15 | ... = ... |
| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:36:5:36:7 | tmp |
| type_tracker.rb:36:5:36:10 | ...[...] | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:36:9:36:9 | 0 | type_tracker.rb:36:9:36:9 | 0 |
| type_tracker.rb:38:5:38:9 | array | type_tracker.rb:38:5:38:9 | array |
| type_tracker.rb:38:13:38:25 | Array | type_tracker.rb:38:13:38:25 | Array |
| type_tracker.rb:38:13:38:25 | call to [] | type_tracker.rb:38:5:38:25 | ... = ... |
| type_tracker.rb:38:13:38:25 | call to [] | type_tracker.rb:38:5:38:25 | ... = ... |
| type_tracker.rb:38:13:38:25 | call to [] | type_tracker.rb:38:13:38:25 | call to [] |
| type_tracker.rb:38:13:38:25 | call to [] | type_tracker.rb:39:5:39:9 | array |
| type_tracker.rb:38:13:38:25 | call to [] | type_tracker.rb:40:5:40:9 | array |
| type_tracker.rb:38:14:38:14 | 1 | type_tracker.rb:38:14:38:14 | 1 |
| type_tracker.rb:38:14:38:14 | 1 | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:38:16:38:16 | 2 | type_tracker.rb:38:16:38:16 | 2 |
| type_tracker.rb:38:18:38:18 | 3 | type_tracker.rb:38:18:38:18 | 3 |
| type_tracker.rb:38:20:38:20 | 4 | type_tracker.rb:38:20:38:20 | 4 |
| type_tracker.rb:38:22:38:22 | 5 | type_tracker.rb:38:22:38:22 | 5 |
| type_tracker.rb:38:24:38:24 | 6 | type_tracker.rb:38:24:38:24 | 6 |
| type_tracker.rb:39:5:39:9 | [post] array | type_tracker.rb:39:5:39:9 | [post] array |
| type_tracker.rb:39:5:39:9 | [post] array | type_tracker.rb:40:5:40:9 | array |
| type_tracker.rb:39:5:39:12 | call to []= | type_tracker.rb:39:5:39:12 | call to []= |
| type_tracker.rb:39:16:39:18 | __synth__0 | type_tracker.rb:39:16:39:18 | __synth__0 |
| type_tracker.rb:40:5:40:12 | ...[...] | type_tracker.rb:40:5:40:12 | ...[...] |
| type_tracker.rb:40:11:40:11 | 0 | type_tracker.rb:40:11:40:11 | 0 |
| type_tracker.rb:42:5:42:10 | array2 | type_tracker.rb:42:5:42:10 | array2 |
| type_tracker.rb:42:14:42:26 | Array | type_tracker.rb:42:14:42:26 | Array |
| type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:42:5:42:26 | ... = ... |
| type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:42:5:42:26 | ... = ... |
| type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:42:14:42:26 | call to [] |
| type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:43:5:43:10 | array2 |
| type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:44:5:44:10 | array2 |
| type_tracker.rb:42:15:42:15 | 1 | type_tracker.rb:42:15:42:15 | 1 |
| type_tracker.rb:42:15:42:15 | 1 | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:17:42:17 | 2 | type_tracker.rb:42:17:42:17 | 2 |
| type_tracker.rb:42:17:42:17 | 2 | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:19:42:19 | 3 | type_tracker.rb:42:19:42:19 | 3 |
| type_tracker.rb:42:19:42:19 | 3 | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:21:42:21 | 4 | type_tracker.rb:42:21:42:21 | 4 |
| type_tracker.rb:42:21:42:21 | 4 | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:23:42:23 | 5 | type_tracker.rb:42:23:42:23 | 5 |
| type_tracker.rb:42:23:42:23 | 5 | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:42:25:42:25 | 6 | type_tracker.rb:42:25:42:25 | 6 |
| type_tracker.rb:42:25:42:25 | 6 | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:43:5:43:10 | [post] array2 | type_tracker.rb:43:5:43:10 | [post] array2 |
| type_tracker.rb:43:5:43:10 | [post] array2 | type_tracker.rb:44:5:44:10 | array2 |
| type_tracker.rb:43:5:43:13 | call to []= | type_tracker.rb:43:5:43:13 | call to []= |
| type_tracker.rb:43:12:43:12 | 0 | type_tracker.rb:43:12:43:12 | 0 |
| type_tracker.rb:43:17:43:19 | __synth__0 | type_tracker.rb:43:17:43:19 | __synth__0 |
| type_tracker.rb:44:5:44:13 | ...[...] | type_tracker.rb:44:5:44:13 | ...[...] |
| type_tracker.rb:46:5:46:10 | array3 | type_tracker.rb:46:5:46:10 | array3 |
| type_tracker.rb:46:14:46:26 | Array | type_tracker.rb:46:14:46:26 | Array |
| type_tracker.rb:46:14:46:26 | call to [] | type_tracker.rb:46:5:46:26 | ... = ... |
| type_tracker.rb:46:14:46:26 | call to [] | type_tracker.rb:46:5:46:26 | ... = ... |
| type_tracker.rb:46:14:46:26 | call to [] | type_tracker.rb:46:14:46:26 | call to [] |
| type_tracker.rb:46:14:46:26 | call to [] | type_tracker.rb:47:5:47:10 | array3 |
| type_tracker.rb:46:14:46:26 | call to [] | type_tracker.rb:48:5:48:10 | array3 |
| type_tracker.rb:46:15:46:15 | 1 | type_tracker.rb:46:15:46:15 | 1 |
| type_tracker.rb:46:17:46:17 | 2 | type_tracker.rb:46:17:46:17 | 2 |
| type_tracker.rb:46:17:46:17 | 2 | type_tracker.rb:48:5:48:13 | ...[...] |
| type_tracker.rb:46:19:46:19 | 3 | type_tracker.rb:46:19:46:19 | 3 |
| type_tracker.rb:46:21:46:21 | 4 | type_tracker.rb:46:21:46:21 | 4 |
| type_tracker.rb:46:23:46:23 | 5 | type_tracker.rb:46:23:46:23 | 5 |
| type_tracker.rb:46:25:46:25 | 6 | type_tracker.rb:46:25:46:25 | 6 |
| type_tracker.rb:47:5:47:10 | [post] array3 | type_tracker.rb:47:5:47:10 | [post] array3 |
| type_tracker.rb:47:5:47:10 | [post] array3 | type_tracker.rb:48:5:48:10 | array3 |
| type_tracker.rb:47:5:47:13 | call to []= | type_tracker.rb:47:5:47:13 | call to []= |
| type_tracker.rb:47:12:47:12 | 0 | type_tracker.rb:47:12:47:12 | 0 |
| type_tracker.rb:47:17:47:19 | __synth__0 | type_tracker.rb:47:17:47:19 | __synth__0 |
| type_tracker.rb:48:5:48:13 | ...[...] | type_tracker.rb:48:5:48:13 | ...[...] |
| type_tracker.rb:48:12:48:12 | 1 | type_tracker.rb:48:12:48:12 | 1 |
| type_tracker.rb:50:5:50:10 | array4 | type_tracker.rb:50:5:50:10 | array4 |
| type_tracker.rb:50:14:50:26 | Array | type_tracker.rb:50:14:50:26 | Array |
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:50:5:50:26 | ... = ... |
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:50:5:50:26 | ... = ... |
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:50:14:50:26 | call to [] |
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:51:5:51:10 | array4 |
| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:52:5:52:10 | array4 |
| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:50:15:50:15 | 1 |
| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:50:17:50:17 | 2 |
| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:50:19:50:19 | 3 |
| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:50:21:50:21 | 4 |
| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:50:23:50:23 | 5 |
| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:50:25:50:25 | 6 |
| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:52:5:52:13 | ...[...] |
| type_tracker.rb:51:5:51:10 | [post] array4 | type_tracker.rb:51:5:51:10 | [post] array4 |
| type_tracker.rb:51:5:51:10 | [post] array4 | type_tracker.rb:52:5:52:10 | array4 |
| type_tracker.rb:51:5:51:13 | call to []= | type_tracker.rb:51:5:51:13 | call to []= |
| type_tracker.rb:51:17:51:19 | __synth__0 | type_tracker.rb:51:17:51:19 | __synth__0 |
| type_tracker.rb:52:5:52:13 | ...[...] | type_tracker.rb:34:1:53:3 | return return in throughArray |
| type_tracker.rb:52:5:52:13 | ...[...] | type_tracker.rb:52:5:52:13 | ...[...] |
forwardButNoBackwardFlow
backwardButNoForwardFlow

View File

@@ -19,3 +19,27 @@ query predicate trackEnd(LocalSourceNode src, DataFlow::Node dst) {
end.flowsTo(dst)
)
}
predicate backtrack(LocalSourceNode sink, TypeBackTracker t, LocalSourceNode src) {
t.start() and
sink = src
or
exists(TypeBackTracker t2, LocalSourceNode mid |
backtrack(sink, t2, mid) and
src = mid.backtrack(t2, t)
)
}
predicate backtrackEnd(LocalSourceNode sink, LocalSourceNode src) {
backtrack(sink, TypeBackTracker::end(), src)
}
query predicate forwardButNoBackwardFlow(LocalSourceNode src, LocalSourceNode sink) {
trackEnd(src, sink) and
not backtrackEnd(sink, src)
}
query predicate backwardButNoForwardFlow(LocalSourceNode src, LocalSourceNode sink) {
backtrackEnd(sink, src) and
not trackEnd(src, sink)
}

View File

@@ -30,3 +30,24 @@ end
keyword(p1: 3, p2: 4)
keyword(p2: 5, p1: 6)
keyword(:p2 => 7, :p1 => 8)
def throughArray(obj, y, z)
tmp = [obj]
tmp[0]
array = [1,2,3,4,5,6]
array[y] = obj
array[0]
array2 = [1,2,3,4,5,6]
array2[0] = obj
array2[y]
array3 = [1,2,3,4,5,6]
array3[0] = obj
array3[1]
array4 = [1,2,3,4,5,6]
array4[y] = obj
array4[z]
end