diff --git a/python/ql/lib/semmle/python/dataflow/new/TypeTracker.qll b/python/ql/lib/semmle/python/dataflow/new/TypeTracker.qll index a27c33145f0f..c038e93770d3 100644 --- a/python/ql/lib/semmle/python/dataflow/new/TypeTracker.qll +++ b/python/ql/lib/semmle/python/dataflow/new/TypeTracker.qll @@ -5,12 +5,13 @@ private import python private import internal.TypeTracker as Internal +private import internal.TypeTrackerSpecific as InternalSpecific /** A string that may appear as the name of an attribute or access path. */ -class AttributeName = Internal::ContentName; +class AttributeName = InternalSpecific::TypeTrackerContent; /** An attribute name, or the empty string (representing no attribute). */ -class OptionalAttributeName = Internal::OptionalContentName; +class OptionalAttributeName = InternalSpecific::OptionalTypeTrackerContent; /** * The summary of the steps needed to track a value to a given dataflow node. diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll index c463d2139207..d689c9003813 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll @@ -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) or + LoadStep(TypeTrackerContent 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,12 @@ 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) /** * A summary of the steps needed to track a value to a given dataflow node. @@ -240,7 +249,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 +260,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 +272,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 +305,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 +313,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 +371,8 @@ module TypeTracker { TypeTracker end() { result.end() } } -private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, OptionalContentName content) +private newtype TTypeBackTracker = + MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) /** * A summary of the steps needed to back-track a use of a value to a given dataflow node. @@ -390,7 +406,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 +417,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 +429,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 +449,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 diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll index d46530bddcbf..ecf7e4ccca1f 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll @@ -11,6 +11,28 @@ class Node = DataFlowPublic::Node; class TypeTrackingNode = DataFlowPublic::TypeTrackingNode; +/** A content name for use by type trackers, or the empty string. */ +class OptionalTypeTrackerContent extends string { + OptionalTypeTrackerContent() { + this = "" + or + this = getPossibleContentName() + } +} + +/** A content name for use by type trackers. */ +class TypeTrackerContent extends OptionalTypeTrackerContent { + TypeTrackerContent() { this != "" } +} + +/** Gets the content string representing no value. */ +OptionalTypeTrackerContent noContent() { result = "" } + +pragma[inline] +predicate compatibleContents(TypeTrackerContent storeContent, TypeTrackerContent loadContent) { + storeContent = loadContent +} + predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStepForTypetracking/2; predicate jumpStep = DataFlowPrivate::jumpStepSharedWithTypeTracker/2; diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 8cec5f0d1906..25daa760d8ee 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -9,6 +9,7 @@ private import ruby 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 | diff --git a/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll index 0562f798d7e2..e9f7bff0f8b6 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll @@ -131,7 +131,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; diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll index 6c8696afd3ea..7ab4f68a409e 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll @@ -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() } } /** @@ -375,7 +378,7 @@ private module Cached { or exists(LibraryCallable callable | result = TLibraryCallable(callable) and - call.asCall().getExpr() = callable.getACall() + call.asCall().getExpr() = [callable.getACall(), callable.getACallSimple()] ) } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index 34d737e8c975..82a1bce4015c 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -1,6 +1,7 @@ private import ruby private import codeql.ruby.ast.internal.Synthesis private import codeql.ruby.CFG +private import codeql.ruby.AST as AST private import codeql.ruby.dataflow.SSA private import DataFlowPublic private import DataFlowDispatch @@ -358,16 +359,20 @@ 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 TElementLowerBoundContent(int lower) { FlowSummaryImplSpecific::ParsePositions::isParsedElementLowerBoundPosition(_, lower) - } + } or + TNoContentSet() + + cached + class TContentSet = TSingletonContent or TAnyElementContent or TElementLowerBoundContent; cached newtype TContent = @@ -393,7 +398,9 @@ private module Cached { | name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim() ) - } + } or + // Only used by type-tracking + TAttributeName(string name) { name = any(AST::SetterMethodCall c).getTargetName() } /** * Holds if `e` is an `ExprNode` that may be returned by a call to `c`. diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll index 5c99237193f1..804c84ae4789 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -314,6 +314,36 @@ 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 } +} + +class OptionalContentSet extends TOptionalContentSet { + /** Gets a textual representation of this content set. */ + string toString() { + result = "no content" // overridden in `ContentSet` + } + + /** Holds if this is the special "no content set" value. */ + predicate isNoContentSet() { this instanceof TNoContentSet } } /** @@ -322,7 +352,7 @@ module Content { * The set may be interpreted differently depending on whether it is * stored into (`getAStoreContent`) or read from (`getAReadContent`). */ -class ContentSet extends TContentSet { +class ContentSet extends OptionalContentSet, TContentSet { /** Holds if this content set is the singleton `{c}`. */ predicate isSingleton(Content c) { this = TSingletonContent(c) } @@ -335,8 +365,7 @@ class ContentSet extends TContentSet { */ predicate isElementLowerBound(int lower) { this = TElementLowerBoundContent(lower) } - /** Gets a textual representation of this content set. */ - string toString() { + override string toString() { exists(Content c | this.isSingleton(c) and result = c.toString() diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Array.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Array.qll index ba7564a65e67..0eb7bb24516e 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Array.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Array.qll @@ -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. */ diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll index cf0872e770ef..c7e072efe33d 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll @@ -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 diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll index b3a0fab20fde..6e08a9fcb7a1 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -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 diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index c463d2139207..d689c9003813 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -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) or + LoadStep(TypeTrackerContent 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,12 @@ 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) /** * A summary of the steps needed to track a value to a given dataflow node. @@ -240,7 +249,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 +260,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 +272,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 +305,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 +313,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 +371,8 @@ module TypeTracker { TypeTracker end() { result.end() } } -private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, OptionalContentName content) +private newtype TTypeBackTracker = + MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) /** * A summary of the steps needed to back-track a use of a value to a given dataflow node. @@ -390,7 +406,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 +417,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 +429,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 +449,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 diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 148bcc3ad5c2..b610e2444948 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -1,16 +1,35 @@ 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; + +class OptionalTypeTrackerContent = DataFlowPublic::OptionalContentSet; + +/** + * 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.isNoContentSet() } + /** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2; @@ -37,14 +56,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 +126,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 +149,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, TypeTrackerContent 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, TypeTrackerContent 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 +188,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, TypeTrackerContent 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 +216,53 @@ 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, TypeTrackerContent contents, SummaryComponent input, + SummaryComponent output +) { + callable + .propagatesFlow(singleton(input), + push(SummaryComponent::content(contents), singleton(output)), true) +} + +private predicate hasLoadSummary( + SummarizedCallable callable, TypeTrackerContent 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) | + exists(int i | + pos.isPositional(i) and + result = call.getPositionalArgument(i) + ) + or + exists(string name | + pos.isKeyword(name) and + result = call.getKeywordArgument(name) + ) + or + pos.isBlock() and + result = call.getBlock() + or + pos.isSelf() and + result = call.getReceiver() + ) + or + component = SummaryComponent::return() and + result = call +} diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.expected b/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.expected index b5ed08cb198a..43b6490b0522 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.expected +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.expected @@ -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 | diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.ql b/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.ql index 275be42ec49f..b12b5c6eebb7 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.ql +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.ql @@ -2,6 +2,8 @@ * Tests of the public API of API Graphs */ +import ruby +import codeql.ruby.DataFlow import codeql.ruby.ApiGraphs query predicate classMethodCalls(API::Node node) { @@ -11,3 +13,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() +} diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb b/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb index daea2a4e6bb0..6d9df028f02c 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb @@ -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) diff --git a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.expected b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.expected index 43b769520150..cb111adde1b0 100644 --- a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.expected +++ b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.expected @@ -34,6 +34,12 @@ edges | summaries.rb:1:11:1:36 | call to identity : | summaries.rb:117:26:117:32 | tainted | | summaries.rb:1:11:1:36 | call to identity : | summaries.rb:119:23:119:29 | tainted | | summaries.rb:1:11:1:36 | call to identity : | summaries.rb:119:23:119:29 | tainted | +| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:122:26:122:32 | tainted | +| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:122:26:122:32 | tainted | +| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:124:16:124:22 | tainted | +| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:124:16:124:22 | tainted | +| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:127:39:127:45 | tainted | +| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:127:39:127: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 | @@ -112,6 +118,9 @@ edges | summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:114:21:114:27 | tainted | | summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:117:26:117:32 | tainted | | summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:119:23:119:29 | tainted | +| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:122:26:122:32 | tainted | +| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:124:16:124:22 | tainted | +| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:127:39:127:45 | tainted | | summaries.rb:104:16:104:22 | tainted : | summaries.rb:104:16:104:22 | [post] tainted : | | summaries.rb:104:16:104:22 | tainted : | summaries.rb:104:25:104:25 | [post] y : | | summaries.rb:104:16:104:22 | tainted : | summaries.rb:104:33:104:33 | [post] z : | @@ -247,6 +256,12 @@ nodes | summaries.rb:117:26:117:32 | tainted | semmle.label | tainted | | summaries.rb:119:23:119:29 | tainted | semmle.label | tainted | | summaries.rb:119:23:119:29 | tainted | semmle.label | tainted | +| summaries.rb:122:26:122:32 | tainted | semmle.label | tainted | +| summaries.rb:122:26:122:32 | tainted | semmle.label | tainted | +| summaries.rb:124:16:124:22 | tainted | semmle.label | tainted | +| summaries.rb:124:16:124:22 | tainted | semmle.label | tainted | +| summaries.rb:127:39:127:45 | tainted | semmle.label | tainted | +| summaries.rb:127:39:127:45 | tainted | semmle.label | tainted | subpaths invalidSpecComponent #select @@ -306,6 +321,12 @@ invalidSpecComponent | summaries.rb:117:26:117:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:117:26:117:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : | | summaries.rb:119:23:119:29 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:119:23:119:29 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : | | summaries.rb:119:23:119:29 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:119:23:119:29 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : | +| summaries.rb:122:26:122:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:122:26:122:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : | +| summaries.rb:122:26:122:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:122:26:122:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : | +| summaries.rb:124:16:124:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:124:16:124:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : | +| summaries.rb:124:16:124:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:124:16:124:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : | +| summaries.rb:127:39:127:45 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:127:39:127:45 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : | +| summaries.rb:127:39:127:45 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:127:39:127: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 | diff --git a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql index 023ba4528515..c187fc513f48 100644 --- a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql +++ b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql @@ -132,6 +132,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", // ] } } diff --git a/ruby/ql/test/library-tests/dataflow/summaries/summaries.rb b/ruby/ql/test/library-tests/dataflow/summaries/summaries.rb index 51587ab050df..fabfbf7c40e5 100644 --- a/ruby/ql/test/library-tests/dataflow/summaries/summaries.rb +++ b/ruby/ql/test/library-tests/dataflow/summaries/summaries.rb @@ -118,3 +118,12 @@ def userDefinedFunction(x, y) "magic_string".method(tainted) # $ hasValueFlow=tainted "magic_string2".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 diff --git a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected index e9e183e0332d..fe611e54227c 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected @@ -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,63 +147,128 @@ 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:45:3 | &block | type tracker without call steps | type_tracker.rb:34:1:45:3 | &block | +| type_tracker.rb:34:1:45:3 | return return in throughArray | type tracker without call steps | type_tracker.rb:34:1:45:3 | return return in throughArray | +| type_tracker.rb:34:1:45:3 | self in throughArray | type tracker without call steps | type_tracker.rb:34:1:45:3 | self in throughArray | +| type_tracker.rb:34:1:45:3 | throughArray | type tracker without call steps | type_tracker.rb:34:1:45: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 with content attribute [] | type_tracker.rb:39:5:39:9 | [post] array | +| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content attribute [] | type_tracker.rb:43:5:43:10 | [post] array2 | +| 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 0 | 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 | type_tracker.rb:42:14:42:26 | call to [] | +| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:45: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 with content attribute [] | type_tracker.rb:39:5:39:9 | [post] array | +| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content attribute [] | type_tracker.rb:43:5:43:10 | [post] array2 | +| 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 0 | 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 | type_tracker.rb:42:14:42: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: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 | 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 | 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 | 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 | 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 | 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 | 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:34:1:45:3 | return return in throughArray | +| 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 | 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:34:1:45:3 | return return in throughArray | +| 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 | 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:34:1:45:3 | return return in throughArray | +| 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 | 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:34:1:45:3 | return return in throughArray | +| 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 | 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:34:1:45:3 | return return in throughArray | +| 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 | 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:34:1:45:3 | return return in throughArray | +| 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 | 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:34:1:45:3 | return return in throughArray | +| type_tracker.rb:44:5:44:13 | ...[...] | type tracker without call steps | type_tracker.rb:44:5:44: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) | | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:18:1:21:3 | self in positional | -| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:19:5:19:11 | self | -| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:20:5:20:11 | self | -| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:23:1:23:16 | self | | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:25:1:28:3 | self (keyword) | | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:25:1:28:3 | self in keyword | -| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:26:5:26:11 | self | -| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:27:5:27:11 | self | -| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:30:1:30:21 | self | -| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:31:1:31:21 | self | -| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:32:1:32:27 | self | | type_tracker.rb:2:5:5:7 | &block | type_tracker.rb:2:5:5:7 | &block | | type_tracker.rb:2:5:5:7 | field= | type_tracker.rb:2:5:5:7 | field= | | type_tracker.rb:2:5:5:7 | return return in field= | type_tracker.rb:2:5:5:7 | return return in field= | | type_tracker.rb:2:5:5:7 | return return in field= | type_tracker.rb:14:5:14:13 | call to field= | | type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:2:5:5:7 | self (field=) | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:3:9:3:23 | self | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:3:14:3:17 | self | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:4:9:4:14 | self | | type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:7:5:9:7 | self in field | -| type_tracker.rb:2:5:5:7 | self (field=) | type_tracker.rb:8:9:8:14 | self | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:2:5:5:7 | self (field=) | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:2:5:5:7 | self in field= | -| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:3:9:3:23 | self | -| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:3:14:3:17 | self | -| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:4:9:4:14 | self | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:7:5:9:7 | self in field | -| type_tracker.rb:2:5:5:7 | self in field= | type_tracker.rb:8:9:8:14 | self | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:5:5:7 | return return in field= | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:2:16:2:18 | val | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:9:4:20 | ... = ... | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val | -| type_tracker.rb:2:16:2:18 | val | type_tracker.rb:4:18:4:20 | val | | type_tracker.rb:2:16:2:18 | val | type_tracker.rb:14:5:14:13 | call to field= | | type_tracker.rb:3:9:3:23 | call to puts | type_tracker.rb:3:9:3:23 | call to puts | | type_tracker.rb:3:14:3:23 | call to field | type_tracker.rb:3:14:3:23 | call to field | | type_tracker.rb:4:9:4:14 | @field | type_tracker.rb:4:9:4:14 | @field | | type_tracker.rb:7:5:9:7 | &block | type_tracker.rb:7:5:9:7 | &block | -| type_tracker.rb:7:5:9:7 | field | type_tracker.rb:1:1:10:3 | Container | | type_tracker.rb:7:5:9:7 | field | type_tracker.rb:7:5:9:7 | field | | type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:3:14:3:23 | call to field | | type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:7:5:9:7 | return return in field | | type_tracker.rb:7:5:9:7 | return return in field | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:7:5:9:7 | self (field) | type_tracker.rb:7:5:9:7 | self (field) | -| type_tracker.rb:7:5:9:7 | self (field) | type_tracker.rb:8:9:8:14 | self | | type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:7:5:9:7 | self in field | -| type_tracker.rb:7:5:9:7 | self in field | type_tracker.rb:8:9:8:14 | self | | type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:3:14:3:23 | call to field | | type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:7:5:9:7 | return return in field | | type_tracker.rb:8:9:8:14 | @field | type_tracker.rb:8:9:8:14 | @field | @@ -212,41 +277,23 @@ trackEnd | type_tracker.rb:12:1:16:3 | m | type_tracker.rb:12:1:16:3 | m | | type_tracker.rb:12:1:16:3 | return return in m | type_tracker.rb:12:1:16:3 | return return in m | | type_tracker.rb:12:1:16:3 | self (m) | type_tracker.rb:12:1:16:3 | self (m) | -| type_tracker.rb:12:1:16:3 | self (m) | type_tracker.rb:15:5:15:18 | self | | type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:12:1:16:3 | self (m) | | type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:12:1:16:3 | self in m | -| type_tracker.rb:12:1:16:3 | self in m | type_tracker.rb:15:5:15:18 | self | | type_tracker.rb:13:5:13:7 | var | type_tracker.rb:13:5:13:7 | var | | type_tracker.rb:13:11:13:19 | Container | type_tracker.rb:13:11:13:19 | Container | | type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:2:5:5:7 | self (field=) | | type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:2:5:5:7 | self in field= | -| type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:3:9:3:23 | self | -| type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:3:14:3:17 | self | -| type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:4:9:4:14 | self | | type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:7:5:9:7 | self in field | -| type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:8:9:8:14 | self | -| type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:13:5:13:23 | ... = ... | -| type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:13:5:13:23 | ... = ... | | type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:13:11:13:23 | call to new | -| type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:14:5:14:7 | var | -| type_tracker.rb:13:11:13:23 | call to new | type_tracker.rb:15:10:15:12 | var | | type_tracker.rb:14:5:14:7 | [post] var | type_tracker.rb:7:5:9:7 | self (field) | | type_tracker.rb:14:5:14:7 | [post] var | type_tracker.rb:7:5:9:7 | self in field | -| type_tracker.rb:14:5:14:7 | [post] var | type_tracker.rb:8:9:8:14 | self | | type_tracker.rb:14:5:14:7 | [post] var | type_tracker.rb:14:5:14:7 | [post] var | -| type_tracker.rb:14:5:14:7 | [post] var | type_tracker.rb:15:10:15:12 | var | | type_tracker.rb:14:5:14:13 | call to field= | type_tracker.rb:14:5:14:13 | call to field= | | type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:2:16:2:18 | val | | type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:2:16:2:18 | val | -| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:4:9:4:20 | ... = ... | -| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:4:18:4:20 | val | -| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:5:14:13 | __synth__0 | | type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:5:14:13 | call to field= | -| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:5:14:23 | ... | | type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:17:14:23 | "hello" | -| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:17:14:23 | ... = ... | -| type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:14:17:14:23 | ... = ... | | type_tracker.rb:14:17:14:23 | "hello" | type_tracker.rb:15:10:15:18 | call to field | | type_tracker.rb:14:17:14:23 | __synth__0 | type_tracker.rb:14:17:14:23 | __synth__0 | | type_tracker.rb:15:5:15:18 | call to puts | type_tracker.rb:12:1:16:3 | return return in m | @@ -257,24 +304,16 @@ trackEnd | type_tracker.rb:18:1:21:3 | return return in positional | type_tracker.rb:18:1:21:3 | return return in positional | | type_tracker.rb:18:1:21:3 | return return in positional | type_tracker.rb:23:1:23:16 | call to positional | | type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:18:1:21:3 | self (positional) | -| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:19:5:19:11 | self | -| type_tracker.rb:18:1:21:3 | self (positional) | type_tracker.rb:20:5:20:11 | self | | type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:18:1:21:3 | self (positional) | | type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:18:1:21:3 | self in positional | -| type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:19:5:19:11 | self | -| type_tracker.rb:18:1:21:3 | self in positional | type_tracker.rb:20:5:20:11 | self | | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:18:16:18:17 | p1 | -| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:19:10:19:11 | p1 | -| type_tracker.rb:18:16:18:17 | p1 | type_tracker.rb:19:10:19:11 | p1 | | type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 | | type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 | | type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 | | type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:18:20:18:21 | p2 | -| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:20:10:20:11 | p2 | -| type_tracker.rb:18:20:18:21 | p2 | type_tracker.rb:20:10:20:11 | p2 | | type_tracker.rb:19:5:19:11 | call to puts | type_tracker.rb:19:5:19:11 | call to puts | | type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:18:1:21:3 | return return in positional | | type_tracker.rb:20:5:20:11 | call to puts | type_tracker.rb:20:5:20:11 | call to puts | @@ -282,11 +321,9 @@ trackEnd | type_tracker.rb:23:1:23:16 | call to positional | type_tracker.rb:23:1:23:16 | call to positional | | type_tracker.rb:23:12:23:12 | 1 | type_tracker.rb:18:16:18:17 | p1 | | type_tracker.rb:23:12:23:12 | 1 | type_tracker.rb:18:16:18:17 | p1 | -| type_tracker.rb:23:12:23:12 | 1 | type_tracker.rb:19:10:19:11 | p1 | | type_tracker.rb:23:12:23:12 | 1 | type_tracker.rb:23:12:23:12 | 1 | | type_tracker.rb:23:15:23:15 | 2 | type_tracker.rb:18:20:18:21 | p2 | | type_tracker.rb:23:15:23:15 | 2 | type_tracker.rb:18:20:18:21 | p2 | -| type_tracker.rb:23:15:23:15 | 2 | type_tracker.rb:20:10:20:11 | p2 | | type_tracker.rb:23:15:23:15 | 2 | type_tracker.rb:23:15:23:15 | 2 | | type_tracker.rb:25:1:28:3 | &block | type_tracker.rb:25:1:28:3 | &block | | type_tracker.rb:25:1:28:3 | keyword | type_tracker.rb:25:1:28:3 | keyword | @@ -295,24 +332,16 @@ trackEnd | type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:31:1:31:21 | call to keyword | | type_tracker.rb:25:1:28:3 | return return in keyword | type_tracker.rb:32:1:32:27 | call to keyword | | type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:25:1:28:3 | self (keyword) | -| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:26:5:26:11 | self | -| type_tracker.rb:25:1:28:3 | self (keyword) | type_tracker.rb:27:5:27:11 | self | | type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:25:1:28:3 | self (keyword) | | type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:25:1:28:3 | self in keyword | -| type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:26:5:26:11 | self | -| type_tracker.rb:25:1:28:3 | self in keyword | type_tracker.rb:27:5:27:11 | self | | type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:25:13:25:14 | p1 | -| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:26:10:26:11 | p1 | -| type_tracker.rb:25:13:25:14 | p1 | type_tracker.rb:26:10:26:11 | p1 | | type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:25:18:25:19 | p2 | -| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:27:10:27:11 | p2 | -| type_tracker.rb:25:18:25:19 | p2 | type_tracker.rb:27:10:27:11 | p2 | | type_tracker.rb:26:5:26:11 | call to puts | type_tracker.rb:26:5:26:11 | call to puts | | type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:25:1:28:3 | return return in keyword | | type_tracker.rb:27:5:27:11 | call to puts | type_tracker.rb:27:5:27:11 | call to puts | @@ -324,37 +353,99 @@ trackEnd | type_tracker.rb:30:9:30:13 | Pair | type_tracker.rb:30:9:30:13 | Pair | | type_tracker.rb:30:13:30:13 | 3 | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:30:13:30:13 | 3 | type_tracker.rb:25:13:25:14 | p1 | -| type_tracker.rb:30:13:30:13 | 3 | type_tracker.rb:26:10:26:11 | p1 | | type_tracker.rb:30:13:30:13 | 3 | type_tracker.rb:30:13:30:13 | 3 | | type_tracker.rb:30:16:30:17 | :p2 | type_tracker.rb:30:16:30:17 | :p2 | | type_tracker.rb:30:16:30:20 | Pair | type_tracker.rb:30:16:30:20 | Pair | | type_tracker.rb:30:20:30:20 | 4 | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:30:20:30:20 | 4 | type_tracker.rb:25:18:25:19 | p2 | -| type_tracker.rb:30:20:30:20 | 4 | type_tracker.rb:27:10:27:11 | p2 | | type_tracker.rb:30:20:30:20 | 4 | type_tracker.rb:30:20:30:20 | 4 | | type_tracker.rb:31:1:31:21 | call to keyword | type_tracker.rb:31:1:31:21 | call to keyword | | type_tracker.rb:31:9:31:10 | :p2 | type_tracker.rb:31:9:31:10 | :p2 | | type_tracker.rb:31:9:31:13 | Pair | type_tracker.rb:31:9:31:13 | Pair | | type_tracker.rb:31:13:31:13 | 5 | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:31:13:31:13 | 5 | type_tracker.rb:25:18:25:19 | p2 | -| type_tracker.rb:31:13:31:13 | 5 | type_tracker.rb:27:10:27:11 | p2 | | type_tracker.rb:31:13:31:13 | 5 | type_tracker.rb:31:13:31:13 | 5 | | type_tracker.rb:31:16:31:17 | :p1 | type_tracker.rb:31:16:31:17 | :p1 | | type_tracker.rb:31:16:31:20 | Pair | type_tracker.rb:31:16:31:20 | Pair | | type_tracker.rb:31:20:31:20 | 6 | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:31:20:31:20 | 6 | type_tracker.rb:25:13:25:14 | p1 | -| type_tracker.rb:31:20:31:20 | 6 | type_tracker.rb:26:10:26:11 | p1 | | type_tracker.rb:31:20:31:20 | 6 | type_tracker.rb:31:20:31:20 | 6 | | type_tracker.rb:32:1:32:27 | call to keyword | type_tracker.rb:32:1:32:27 | call to keyword | | type_tracker.rb:32:9:32:11 | :p2 | type_tracker.rb:32:9:32:11 | :p2 | | type_tracker.rb:32:9:32:16 | Pair | type_tracker.rb:32:9:32:16 | Pair | | type_tracker.rb:32:16:32:16 | 7 | type_tracker.rb:25:18:25:19 | p2 | | type_tracker.rb:32:16:32:16 | 7 | type_tracker.rb:25:18:25:19 | p2 | -| type_tracker.rb:32:16:32:16 | 7 | type_tracker.rb:27:10:27:11 | p2 | | type_tracker.rb:32:16:32:16 | 7 | type_tracker.rb:32:16:32:16 | 7 | | type_tracker.rb:32:19:32:21 | :p1 | type_tracker.rb:32:19:32:21 | :p1 | | type_tracker.rb:32:19:32:26 | Pair | type_tracker.rb:32:19:32:26 | Pair | | 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: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:45:3 | &block | type_tracker.rb:34:1:45:3 | &block | +| type_tracker.rb:34:1:45:3 | return return in throughArray | type_tracker.rb:34:1:45:3 | return return in throughArray | +| type_tracker.rb:34:1:45:3 | self in throughArray | type_tracker.rb:34:1:45:3 | self in throughArray | +| type_tracker.rb:34:1:45:3 | throughArray | type_tracker.rb:34:1:45:3 | throughArray | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:45: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: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: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:44:5:44:13 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44: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: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:11:35:15 | call to [] | +| 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:13:38:25 | call to [] | +| 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: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:14:42:26 | call to [] | +| type_tracker.rb:42:15:42:15 | 1 | type_tracker.rb:34:1:45:3 | return return in throughArray | +| 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:34:1:45:3 | return return in throughArray | +| 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:34:1:45:3 | return return in throughArray | +| 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:34:1:45:3 | return return in throughArray | +| 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:34:1:45:3 | return return in throughArray | +| 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:34:1:45:3 | return return in throughArray | +| 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: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:34:1:45:3 | return return in throughArray | +| type_tracker.rb:44:5:44:13 | ...[...] | type_tracker.rb:44:5:44:13 | ...[...] | +forwardButNoBackwardFlow +backwardButNoForwardFlow diff --git a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.ql b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.ql index 1d568a8e3a16..3b923afd3ec7 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.ql +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.ql @@ -6,16 +6,32 @@ class LocalSourceNode extends DataFlow::LocalSourceNode { LocalSourceNode() { this.getLocation().getFile().getExtension() = "rb" } } -query predicate track(LocalSourceNode src, TypeTracker t, LocalSourceNode dst) { +query LocalSourceNode track(LocalSourceNode src, TypeTracker t) { t.start() and - dst = src + result = src or - exists(TypeTracker t2, LocalSourceNode mid | track(src, t2, mid) and dst = mid.track(t2, t)) + exists(TypeTracker t2 | result = track(src, t2).track(t2, t)) } -query predicate trackEnd(LocalSourceNode src, DataFlow::Node dst) { - exists(LocalSourceNode end | - track(src, TypeTracker::end(), end) and - end.flowsTo(dst) - ) +query LocalSourceNode trackEnd(LocalSourceNode src) { result = track(src, TypeTracker::end()) } + +LocalSourceNode backtrack(LocalSourceNode sink, TypeBackTracker t) { + t.start() and + result = sink + or + exists(TypeBackTracker t2 | result = backtrack(sink, t2).backtrack(t2, t)) +} + +LocalSourceNode backtrackEnd(LocalSourceNode sink) { + result = backtrack(sink, TypeBackTracker::end()) +} + +query predicate forwardButNoBackwardFlow(LocalSourceNode src, LocalSourceNode sink) { + sink = trackEnd(src) and + not src = backtrackEnd(sink) +} + +query predicate backwardButNoForwardFlow(LocalSourceNode src, LocalSourceNode sink) { + src = backtrackEnd(sink) and + not sink = trackEnd(src) } diff --git a/ruby/ql/test/library-tests/dataflow/type-tracker/type_tracker.rb b/ruby/ql/test/library-tests/dataflow/type-tracker/type_tracker.rb index e21d56e4caf3..9bdcda383162 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/type_tracker.rb +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/type_tracker.rb @@ -30,3 +30,16 @@ def keyword(p1:, p2:) keyword(p1: 3, p2: 4) keyword(p2: 5, p1: 6) keyword(:p2 => 7, :p1 => 8) + +def throughArray(obj, y) + 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] +end