From 53ef054c535c5054f96cd1f2f49fbb4d39a67be9 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 30 Aug 2022 12:14:21 +0200 Subject: [PATCH 01/39] Ruby: Add getACallSimple and use it for arrays and hashes --- .../lib/codeql/ruby/dataflow/FlowSummary.qll | 2 +- .../dataflow/internal/DataFlowDispatch.qll | 7 +++- .../lib/codeql/ruby/frameworks/core/Array.qll | 42 +++++++++++-------- .../lib/codeql/ruby/frameworks/core/Hash.qll | 30 +++++++------ 4 files changed, 47 insertions(+), 34 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll index 55cad66e060..0f640b7dbdb 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 f6d9704763a..1bcbcddeb36 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() } } /** @@ -287,7 +290,7 @@ private DataFlowCallable viableSourceCallable(DataFlowCall call) { private DataFlowCallable viableLibraryCallable(DataFlowCall call) { exists(LibraryCallable callable | result = TLibraryCallable(callable) and - call.asCall().getExpr() = callable.getACall() + call.asCall().getExpr() = [callable.getACall(), callable.getACallSimple()] ) } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Array.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Array.qll index ba7564a65e6..0eb7bb24516 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 cf0872e770e..bcfba2a16e9 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 an 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 From f1b99e867c8b17052ebdc1267ded98ef1af3b9ad Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 30 Aug 2022 14:34:10 +0200 Subject: [PATCH 02/39] Ruby: use IPA type for type tracker contents fixup qldoc in OptionalTypeTrckerContent --- .../codeql/ruby/typetracking/TypeTracker.qll | 94 +++++++++---------- .../ruby/typetracking/TypeTrackerSpecific.qll | 47 +++++++--- 2 files changed, 80 insertions(+), 61 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index c463d213920..62895a9431e 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,23 +12,27 @@ 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() /** 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 content | + tt = MkTypeTracker(hasCall, content) + | step = LevelStep() and result = tt or step = CallStep() and result = MkTypeTracker(true, content) or step = ReturnStep() and hasCall = false and result = tt or - step = LoadStep(content) and result = MkTypeTracker(hasCall, "") + step = LoadStep(content) and result = MkTypeTracker(hasCall, noContent()) or - exists(string p | step = StoreStep(p) and content = "" and result = MkTypeTracker(hasCall, p)) + exists(TypeTrackerContent p | + step = StoreStep(p) and content = noContent() and result = MkTypeTracker(hasCall, p) + ) or step = JumpStep() and result = MkTypeTracker(false, content) @@ -59,18 +42,20 @@ private module Cached { /** 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) + exists(TypeTrackerContent p | + step = LoadStep(p) and content = noContent() and result = MkTypeBackTracker(hasReturn, p) ) or - step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "") + step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, noContent()) or step = JumpStep() and result = MkTypeBackTracker(false, content) @@ -114,9 +99,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 +115,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 @@ -204,12 +189,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 +225,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 +236,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 +248,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 +281,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 +289,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 +347,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 +382,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 +393,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 +405,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 +425,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 d339d9044db..66eeb8f09f8 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -11,6 +11,37 @@ class Node = DataFlowPublic::Node; class TypeTrackingNode = DataFlowPublic::LocalSourceNode; +private newtype TOptionalTypeTrackerContent = + MkAttribute(string name) { name = getSetterCallAttributeName(_) } or + MkContent(DataFlowPublic::Content content) or + MkNoContent() + +/** A content for use by type trackers, or the empty content `noContent()` */ +class OptionalTypeTrackerContent extends TOptionalTypeTrackerContent { + /** Gets a textual representation of this content. */ + string toString() { + result = "attribute " + this.asAttributeName() + or + result = this.asContent().toString() + or + this instanceof MkNoContent and result = "no content" + } + + /** Gets the attribute name represented by this content, if any. */ + string asAttributeName() { this = MkAttribute(result) } + + /** Gets the data flow content by this type-tracker content, if any. */ + DataFlowPublic::Content asContent() { this = MkContent(result) } +} + +/** Gets the value representing no content, that is, the empty access path. */ +OptionalTypeTrackerContent noContent() { result = MkNoContent() } + +private class TTypeTrackerContent = MkAttribute or MkContent; + +/** A content for use by type trackers. */ +class TypeTrackerContent extends OptionalTypeTrackerContent, TTypeTrackerContent { } + /** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2; @@ -37,14 +68,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, @@ -145,10 +168,10 @@ predicate returnStep(Node nodeFrom, Node nodeTo) { * 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 content) { // TODO: support SetterMethodCall inside TuplePattern exists(ExprNodes::MethodCallCfgNode call | - content = getSetterCallAttributeName(call.getExpr()) and + content = MkAttribute(getSetterCallAttributeName(call.getExpr())) and nodeTo.(DataFlowPublic::PostUpdateNode).getPreUpdateNode().asExpr() = call.getReceiver() and call.getExpr() instanceof Ast::SetterMethodCall and call.getArgument(call.getNumberOfArguments() - 1) = @@ -175,10 +198,10 @@ private string getSetterCallAttributeName(Ast::SetterMethodCall call) { /** * 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 content) { exists(ExprNodes::MethodCallCfgNode call | call.getExpr().getNumberOfArguments() = 0 and - content = call.getExpr().getMethodName() and + content = MkAttribute(call.getExpr().getMethodName()) and nodeFrom.asExpr() = call.getReceiver() and nodeTo.asExpr() = call ) From cd9cddf45a419ac23e101e1dace2a46d0424f502 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 30 Aug 2022 12:56:28 +0200 Subject: [PATCH 03/39] Ruby: generate type-tracking steps from simple summary specs --- .../dataflow/internal/DataFlowPrivate.qll | 2 +- .../ruby/typetracking/TypeTrackerSpecific.qll | 78 +++++++++++++++++++ .../dataflow/api-graphs/ApiGraphs.expected | 2 + .../dataflow/api-graphs/ApiGraphs.ql | 7 ++ .../dataflow/api-graphs/test1.rb | 3 + .../type-tracker/TypeTracker.expected | 47 ++++++++++- .../dataflow/type-tracker/type_tracker.rb | 5 ++ 7 files changed, 140 insertions(+), 4 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index 5652b700851..3caab1cafcd 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -373,7 +373,7 @@ private module Cached { n instanceof SynthReturnNode or // Needed for stores in type tracking - TypeTrackerSpecific::basicStoreStep(_, n, _) + TypeTrackerSpecific::postUpdateStoreStep(_, n, _) } cached diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 66eeb8f09f8..6b4defcf16d 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -1,11 +1,15 @@ 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; @@ -169,6 +173,27 @@ predicate returnStep(Node nodeFrom, Node nodeTo) { * called. */ predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) { + postUpdateStoreStep(nodeFrom, nodeTo, content) + or + exists( + DataFlowPublic::CallNode call, SummaryComponent input, DataFlowPublic::ContentSet contents, + SummaryComponent output + | + summarizableCall(call.asExpr().getExpr(), // + SummaryComponentStack::singleton(input), + SummaryComponentStack::push(SummaryComponent::content(contents), + SummaryComponentStack::singleton(output))) and + nodeFrom = evaluateSummaryComponentLocal(call, input) and + nodeTo = evaluateSummaryComponentLocal(call, output) and + content.asContent() = contents.getAStoreContent() + ) +} + +/** + * A `content`-store step from `nodeFrom -> nodeTo` 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 content) { // TODO: support SetterMethodCall inside TuplePattern exists(ExprNodes::MethodCallCfgNode call | content = MkAttribute(getSetterCallAttributeName(call.getExpr())) and @@ -205,6 +230,19 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) nodeFrom.asExpr() = call.getReceiver() and nodeTo.asExpr() = call ) + or + exists( + DataFlowPublic::CallNode call, SummaryComponent input, DataFlowPublic::ContentSet contents, + SummaryComponent output + | + summarizableCall(call.asExpr().getExpr(), // + SummaryComponentStack::push(SummaryComponent::content(contents), + SummaryComponentStack::singleton(input)), // + SummaryComponentStack::singleton(output)) and + nodeFrom = evaluateSummaryComponentLocal(call, input) and + nodeTo = evaluateSummaryComponentLocal(call, output) and + content.asContent() = contents.getAReadContent() + ) } /** @@ -213,3 +251,43 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) class Boolean extends boolean { Boolean() { this = true or this = false } } + +/** Holds if `call` has a summary consisting of the given `input`/`output` pair. */ +private predicate summarizableCall( + MethodCall call, SummaryComponentStack input, SummaryComponentStack output +) { + exists(SummarizedCallable callable | + call = callable.getACallSimple() and + callable.propagatesFlow(input, 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 b5ed08cb198..43b6490b052 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 275be42ec49..b12b5c6eebb 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 daea2a4e6bb..904b42ba562 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,6 @@ 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() 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 e9e183e0332..e19ca057c33 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,6 +147,25 @@ 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:37:3 | &block | type tracker without call steps | type_tracker.rb:34:1:37:3 | &block | +| type_tracker.rb:34:1:37:3 | return return in throughArray | type tracker without call steps | type_tracker.rb:34:1:37:3 | return return in throughArray | +| type_tracker.rb:34:1:37:3 | self in throughArray | type tracker without call steps | type_tracker.rb:34:1:37:3 | self in throughArray | +| type_tracker.rb:34:1:37:3 | throughArray | type tracker without call steps | type_tracker.rb:34:1:37: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 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 | type_tracker.rb:34:1:37: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 with content element 0 | type_tracker.rb:35:11:35:15 | call to [] | +| 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:34:1:37:3 | return return in throughArray | +| 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 | trackEnd | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:18:1:21:3 | self (positional) | @@ -358,3 +377,25 @@ trackEnd | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:26:10:26:11 | p1 | | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:32:26:32:26 | 8 | +| type_tracker.rb:34:1:37:3 | &block | type_tracker.rb:34:1:37:3 | &block | +| type_tracker.rb:34:1:37:3 | return return in throughArray | type_tracker.rb:34:1:37:3 | return return in throughArray | +| type_tracker.rb:34:1:37:3 | self in throughArray | type_tracker.rb:34:1:37:3 | self in throughArray | +| type_tracker.rb:34:1:37:3 | throughArray | type_tracker.rb:34:1:37:3 | throughArray | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:37:3 | return return in throughArray | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] | +| type_tracker.rb:35:5:35:7 | tmp | type_tracker.rb:35:5:35:7 | tmp | +| type_tracker.rb:35:11:35:15 | Array | type_tracker.rb:35:11:35:15 | Array | +| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:5:35:15 | ... = ... | +| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:5:35:15 | ... = ... | +| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:11:35:15 | call to [] | +| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:36:5:36:7 | tmp | +| type_tracker.rb:36:5:36:10 | ...[...] | type_tracker.rb:34:1:37:3 | return return in throughArray | +| 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 | 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 e21d56e4caf..ad742366a5c 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,8 @@ end keyword(p1: 3, p2: 4) keyword(p2: 5, p1: 6) keyword(:p2 => 7, :p1 => 8) + +def throughArray(obj) + tmp = [obj] + tmp[0] +end From e104b65106de4499dd243b016668006596cf43c6 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 30 Aug 2022 14:38:50 +0200 Subject: [PATCH 04/39] Python: sync TypeTracker.qll and adapt accordingly fixup python --- .../python/dataflow/new/TypeTracker.qll | 5 +- .../dataflow/new/internal/TypeTracker.qll | 94 +++++++++---------- .../new/internal/TypeTrackerSpecific.qll | 17 ++++ 3 files changed, 65 insertions(+), 51 deletions(-) diff --git a/python/ql/lib/semmle/python/dataflow/new/TypeTracker.qll b/python/ql/lib/semmle/python/dataflow/new/TypeTracker.qll index a27c33145f0..c038e93770d 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 c463d213920..62895a9431e 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,23 +12,27 @@ 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() /** 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 content | + tt = MkTypeTracker(hasCall, content) + | step = LevelStep() and result = tt or step = CallStep() and result = MkTypeTracker(true, content) or step = ReturnStep() and hasCall = false and result = tt or - step = LoadStep(content) and result = MkTypeTracker(hasCall, "") + step = LoadStep(content) and result = MkTypeTracker(hasCall, noContent()) or - exists(string p | step = StoreStep(p) and content = "" and result = MkTypeTracker(hasCall, p)) + exists(TypeTrackerContent p | + step = StoreStep(p) and content = noContent() and result = MkTypeTracker(hasCall, p) + ) or step = JumpStep() and result = MkTypeTracker(false, content) @@ -59,18 +42,20 @@ private module Cached { /** 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) + exists(TypeTrackerContent p | + step = LoadStep(p) and content = noContent() and result = MkTypeBackTracker(hasReturn, p) ) or - step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "") + step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, noContent()) or step = JumpStep() and result = MkTypeBackTracker(false, content) @@ -114,9 +99,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 +115,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 @@ -204,12 +189,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 +225,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 +236,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 +248,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 +281,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 +289,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 +347,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 +382,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 +393,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 +405,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 +425,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 d46530bddcb..b0a8c85c1fc 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,23 @@ 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 != "" } +} + +/** The content string representing no value. */ +OptionalTypeTrackerContent noContent() { result = "" } + predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStepForTypetracking/2; predicate jumpStep = DataFlowPrivate::jumpStepSharedWithTypeTracker/2; From d5e2b93554c340545c69ef3735f65a21fe052e40 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 30 Aug 2022 14:41:33 +0200 Subject: [PATCH 05/39] Ruby: add API graph label for content --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 94b82e85107..0571474b942 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -754,7 +754,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 +845,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 +885,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 | From a51a54058288b92a258d1f822bb49a5af7a88648 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 30 Aug 2022 14:58:36 +0200 Subject: [PATCH 06/39] Ruby: add content edges to API graph Fixes --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 51 +++++++++++++++++++ .../data/internal/ApiGraphModelsSpecific.qll | 13 +++-- .../dataflow/api-graphs/test1.rb | 2 + .../dataflow/summaries/Summaries.expected | 21 ++++++++ .../dataflow/summaries/Summaries.ql | 3 ++ .../dataflow/summaries/summaries.rb | 9 ++++ 6 files changed, 94 insertions(+), 5 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 0571474b942..b1064151467 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -9,6 +9,7 @@ private import codeql.ruby.AST private import codeql.ruby.DataFlow private import codeql.ruby.typetracking.TypeTracker +private import codeql.ruby.typetracking.TypeTrackerSpecific as TypeTrackerSpecific private import codeql.ruby.ast.internal.Module private import codeql.ruby.controlflow.CfgNodes private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate @@ -257,6 +258,32 @@ 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. + */ + Node getContents(DataFlow::ContentSet contents) { + this instanceof API::Use and + result = this.getContent(contents.getAStoreContent()) // The library has stored a value of interest in `contents` + or + this instanceof API::Def and + result = this.getContent(contents.getAReadContent()) // The library going to read a value stored in `contents` + } + + /** 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. */ + 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 +522,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.asContent()) + ) // 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.asContent()) + ) + } + pragma[nomagic] private predicate isUse(DataFlow::Node nd) { useRoot(_, nd) @@ -560,6 +603,8 @@ module API { // 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() } @@ -682,6 +727,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 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 998c24d9105..03390a6c34e 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/test/library-tests/dataflow/api-graphs/test1.rb b/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb index 904b42ba562..6d9df028f02 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb @@ -71,3 +71,5 @@ 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 43b76952015..cb111adde1b 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 e682c120ca5..2ea0a5dc383 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 51587ab050d..fabfbf7c40e 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 @@ Foo.sinkAnyNamedArg(key: tainted) # $ hasValueFlow=tainted "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 From a64f7cd14698500e73fd235deac8e208386da96f Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 1 Sep 2022 11:43:51 +0200 Subject: [PATCH 07/39] Ruby: simplify getSetterCallAttributeName --- ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 6b4defcf16d..77aadddf4c9 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -214,10 +214,7 @@ predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent con * ``` */ 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) - ) + result = call.getTargetName() } /** From ac1b7eb0b986ba9688db5e3118bf9cc7c752e453 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 1 Sep 2022 11:44:40 +0200 Subject: [PATCH 08/39] Remove SetterMethodCall in MkAttribute --- .../ruby/typetracking/TypeTrackerSpecific.qll | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 77aadddf4c9..8d23de60c6d 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -16,7 +16,7 @@ class Node = DataFlowPublic::Node; class TypeTrackingNode = DataFlowPublic::LocalSourceNode; private newtype TOptionalTypeTrackerContent = - MkAttribute(string name) { name = getSetterCallAttributeName(_) } or + MkAttribute(string name) { name = any(Ast::SetterMethodCall c).getTargetName() } or MkContent(DataFlowPublic::Content content) or MkNoContent() @@ -196,7 +196,7 @@ predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) { // TODO: support SetterMethodCall inside TuplePattern exists(ExprNodes::MethodCallCfgNode call | - content = MkAttribute(getSetterCallAttributeName(call.getExpr())) and + content = MkAttribute(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) = @@ -204,19 +204,6 @@ predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent con ) } -/** - * 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) { - result = call.getTargetName() -} - /** * Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`. */ From 497258eda5e373b5cd8353898eb48cd9a3f289b1 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 1 Sep 2022 12:07:55 +0200 Subject: [PATCH 09/39] Ruby: reuse Content type --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 4 +- .../dataflow/internal/DataFlowPrivate.qll | 13 ++++++- .../ruby/dataflow/internal/DataFlowPublic.qll | 35 +++++++++++++++++- .../ruby/typetracking/TypeTrackerSpecific.qll | 37 +++++-------------- 4 files changed, 55 insertions(+), 34 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index b1064151467..344c7f7c44d 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -525,7 +525,7 @@ module API { or exists(TypeTrackerSpecific::TypeTrackerContent c | TypeTrackerSpecific::basicLoadStep(node, ref, c) and - lbl = Label::content(c.asContent()) + lbl = Label::content(c) ) // note: method calls are not handled here as there is no DataFlow::Node for the intermediate MkMethodAccessNode API node } @@ -537,7 +537,7 @@ module API { 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.asContent()) + lbl = Label::content(c) ) } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index 3caab1cafcd..6f43190705f 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 codeql.ruby.AST 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 @@ -385,7 +386,8 @@ private module Cached { } cached - newtype TContent = + newtype TOptionalContent = + TNoContent() or TKnownElementContent(ConstantValue cv) { not cv.isInt(_) or cv.getInt() in [0 .. 10] @@ -408,7 +410,14 @@ private module Cached { | name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim() ) - } + } or + // Only used by type-tracking + TAttributeName(string name) { name = any(AST::SetterMethodCall c).getTargetName() } + + cached + class TContent = + TKnownElementContent or TUnknownElementContent or TKnownPairValueContent or + TUnknownPairValueContent or TFieldContent or TAttributeName; /** * 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 3b0c0094386..d412967306e 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -235,8 +235,8 @@ predicate localExprFlow(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) { localFlow(exprNode(e1), exprNode(e2)) } -/** A reference contained in an object. */ -class Content extends TContent { +/** A reference contained in an object, or the `noContent()` value. */ +class OptionalContent extends TOptionalContent { /** Gets a textual representation of this content. */ string toString() { none() } @@ -244,6 +244,9 @@ class Content extends TContent { Location getLocation() { none() } } +/** A reference contained in an object. */ +class Content extends OptionalContent, TContent { } + /** Provides different sub classes of `Content`. */ module Content { /** An element in a collection, for example an element in an array or in a hash. */ @@ -314,6 +317,34 @@ 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 } + + /** A value representing no content. */ + class NoContent extends OptionalContent, TNoContent { + override string toString() { result = "noContent()" } + } + + /** Gets the `noContent()` value. */ + NoContent noContent() { any() } } /** diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 8d23de60c6d..c0b1ed80c87 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -20,31 +20,9 @@ private newtype TOptionalTypeTrackerContent = MkContent(DataFlowPublic::Content content) or MkNoContent() -/** A content for use by type trackers, or the empty content `noContent()` */ -class OptionalTypeTrackerContent extends TOptionalTypeTrackerContent { - /** Gets a textual representation of this content. */ - string toString() { - result = "attribute " + this.asAttributeName() - or - result = this.asContent().toString() - or - this instanceof MkNoContent and result = "no content" - } +class TypeTrackerContent = DataFlowPublic::Content; - /** Gets the attribute name represented by this content, if any. */ - string asAttributeName() { this = MkAttribute(result) } - - /** Gets the data flow content by this type-tracker content, if any. */ - DataFlowPublic::Content asContent() { this = MkContent(result) } -} - -/** Gets the value representing no content, that is, the empty access path. */ -OptionalTypeTrackerContent noContent() { result = MkNoContent() } - -private class TTypeTrackerContent = MkAttribute or MkContent; - -/** A content for use by type trackers. */ -class TypeTrackerContent extends OptionalTypeTrackerContent, TTypeTrackerContent { } +predicate noContent = DataFlowPublic::Content::noContent/0; /** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2; @@ -185,7 +163,7 @@ predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) SummaryComponentStack::singleton(output))) and nodeFrom = evaluateSummaryComponentLocal(call, input) and nodeTo = evaluateSummaryComponentLocal(call, output) and - content.asContent() = contents.getAStoreContent() + content = contents.getAStoreContent() ) } @@ -196,7 +174,10 @@ predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) { // TODO: support SetterMethodCall inside TuplePattern exists(ExprNodes::MethodCallCfgNode call | - content = MkAttribute(call.getExpr().(Ast::SetterMethodCall).getTargetName()) and + content = + 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) = @@ -210,7 +191,7 @@ predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent con predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) { exists(ExprNodes::MethodCallCfgNode call | call.getExpr().getNumberOfArguments() = 0 and - content = MkAttribute(call.getExpr().getMethodName()) and + content = DataFlowPublic::Content::getAttributeName(call.getExpr().getMethodName()) and nodeFrom.asExpr() = call.getReceiver() and nodeTo.asExpr() = call ) @@ -225,7 +206,7 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) SummaryComponentStack::singleton(output)) and nodeFrom = evaluateSummaryComponentLocal(call, input) and nodeTo = evaluateSummaryComponentLocal(call, output) and - content.asContent() = contents.getAReadContent() + content = contents.getAReadContent() ) } From 3498a04b89e95f77161df6e22eb1f6f5d6519545 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 1 Sep 2022 12:16:37 +0200 Subject: [PATCH 10/39] Ruby: associate ContentSets with store/load edges in type tracker --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 15 ++--- .../codeql/ruby/typetracking/TypeTracker.qll | 56 ++++++++++++------- .../ruby/typetracking/TypeTrackerSpecific.qll | 36 +++++------- 3 files changed, 57 insertions(+), 50 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 344c7f7c44d..ecf1e4a9c92 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -269,11 +269,8 @@ module API { * Gets a node representing the `contents` stored on the base object. */ Node getContents(DataFlow::ContentSet contents) { - this instanceof API::Use and - result = this.getContent(contents.getAStoreContent()) // The library has stored a value of interest in `contents` - or - this instanceof API::Def and - result = this.getContent(contents.getAReadContent()) // The library going to read a value stored in `contents` + // We already 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. */ @@ -523,9 +520,9 @@ module API { read = c.getExpr() ) or - exists(TypeTrackerSpecific::TypeTrackerContent c | + exists(TypeTrackerSpecific::TypeTrackerContentSet c | TypeTrackerSpecific::basicLoadStep(node, ref, c) and - lbl = Label::content(c) + lbl = Label::content(c.getAStoreContent()) ) // note: method calls are not handled here as there is no DataFlow::Node for the intermediate MkMethodAccessNode API node } @@ -535,9 +532,9 @@ module API { * 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 | + exists(TypeTrackerSpecific::TypeTrackerContentSet c | TypeTrackerSpecific::basicStoreStep(rhs, node, c) and - lbl = Label::content(c) + lbl = Label::content(c.getAStoreContent()) ) } diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index 62895a9431e..dd3fb6495ee 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -12,30 +12,36 @@ private module Cached { LevelStep() or CallStep() or ReturnStep() or - StoreStep(TypeTrackerContent content) or - LoadStep(TypeTrackerContent content) or + StoreStep(TypeTrackerContentSet contents) or + LoadStep(TypeTrackerContentSet contents) or JumpStep() /** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */ cached TypeTracker append(TypeTracker tt, StepSummary step) { - exists(Boolean hasCall, OptionalTypeTrackerContent content | - tt = MkTypeTracker(hasCall, content) + exists(Boolean hasCall, OptionalTypeTrackerContent currentContent | + tt = MkTypeTracker(hasCall, currentContent) | step = LevelStep() and result = tt or - step = CallStep() and result = MkTypeTracker(true, content) + step = CallStep() and result = MkTypeTracker(true, currentContent) or step = ReturnStep() and hasCall = false and result = tt or - step = LoadStep(content) and result = MkTypeTracker(hasCall, noContent()) + exists(TypeTrackerContentSet contents | + step = LoadStep(contents) and + currentContent = contents.getAReadContent() and + result = MkTypeTracker(hasCall, noContent()) + ) or - exists(TypeTrackerContent p | - step = StoreStep(p) and content = noContent() and result = MkTypeTracker(hasCall, p) + exists(TypeTrackerContentSet contents | + step = StoreStep(contents) and + currentContent = noContent() and + result = MkTypeTracker(hasCall, contents.getAStoreContent()) ) or step = JumpStep() and - result = MkTypeTracker(false, content) + result = MkTypeTracker(false, currentContent) ) } @@ -51,11 +57,17 @@ private module Cached { or step = ReturnStep() and result = MkTypeBackTracker(true, content) or - exists(TypeTrackerContent p | - step = LoadStep(p) and content = noContent() and result = MkTypeBackTracker(hasReturn, p) + exists(TypeTrackerContentSet contents | + step = LoadStep(contents) and + content = noContent() and + result = MkTypeBackTracker(hasReturn, contents.getAStoreContent()) ) or - step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, noContent()) + exists(TypeTrackerContentSet contents | + step = StoreStep(contents) and + content = contents.getAReadContent() and + result = MkTypeBackTracker(hasReturn, noContent()) + ) or step = JumpStep() and result = MkTypeBackTracker(false, content) @@ -99,9 +111,11 @@ class StepSummary extends TStepSummary { or this instanceof ReturnStep and result = "return" or - exists(TypeTrackerContent content | this = StoreStep(content) | result = "store " + content) + exists(TypeTrackerContentSet contents | this = StoreStep(contents) | + result = "store " + contents + ) or - exists(TypeTrackerContent content | this = LoadStep(content) | result = "load " + content) + exists(TypeTrackerContentSet contents | this = LoadStep(contents) | result = "load " + contents) or this instanceof JumpStep and result = "jump" } @@ -115,11 +129,11 @@ private predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSu levelStep(nodeFrom, nodeTo) and summary = LevelStep() or - exists(TypeTrackerContent content | - StepSummary::localSourceStoreStep(nodeFrom, nodeTo, content) and - summary = StoreStep(content) + exists(TypeTrackerContentSet contents | + StepSummary::localSourceStoreStep(nodeFrom, nodeTo, contents) and + summary = StoreStep(contents) or - basicLoadStep(nodeFrom, nodeTo, content) and summary = LoadStep(content) + basicLoadStep(nodeFrom, nodeTo, contents) and summary = LoadStep(contents) ) } @@ -189,8 +203,10 @@ 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, TypeTrackerContent content) { - exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content)) + predicate localSourceStoreStep( + Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContentSet contents + ) { + exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, contents)) } } diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index c0b1ed80c87..6ce18c27d4e 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -22,6 +22,8 @@ private newtype TOptionalTypeTrackerContent = class TypeTrackerContent = DataFlowPublic::Content; +class TypeTrackerContentSet = DataFlowPublic::ContentSet; + predicate noContent = DataFlowPublic::Content::noContent/0; /** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */ @@ -150,20 +152,16 @@ predicate returnStep(Node nodeFrom, Node nodeTo) { * to `z` inside `bar`, even though this content write happens _after_ `bar` is * called. */ -predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) { - postUpdateStoreStep(nodeFrom, nodeTo, content) +predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContentSet contents) { + postUpdateStoreStep(nodeFrom, nodeTo, contents) or - exists( - DataFlowPublic::CallNode call, SummaryComponent input, DataFlowPublic::ContentSet contents, - SummaryComponent output - | + exists(DataFlowPublic::CallNode call, SummaryComponent input, SummaryComponent output | summarizableCall(call.asExpr().getExpr(), // SummaryComponentStack::singleton(input), SummaryComponentStack::push(SummaryComponent::content(contents), SummaryComponentStack::singleton(output))) and nodeFrom = evaluateSummaryComponentLocal(call, input) and - nodeTo = evaluateSummaryComponentLocal(call, output) and - content = contents.getAStoreContent() + nodeTo = evaluateSummaryComponentLocal(call, output) ) } @@ -171,13 +169,13 @@ predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) * A `content`-store step from `nodeFrom -> nodeTo` 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 content) { +predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContentSet contents) { // TODO: support SetterMethodCall inside TuplePattern exists(ExprNodes::MethodCallCfgNode call | - content = - DataFlowPublic::Content::getAttributeName(call.getExpr() - .(Ast::SetterMethodCall) - .getTargetName()) 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) = @@ -188,25 +186,21 @@ predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent con /** * Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`. */ -predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) { +predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContentSet contents) { exists(ExprNodes::MethodCallCfgNode call | call.getExpr().getNumberOfArguments() = 0 and - content = DataFlowPublic::Content::getAttributeName(call.getExpr().getMethodName()) and + contents.isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr().getMethodName())) and nodeFrom.asExpr() = call.getReceiver() and nodeTo.asExpr() = call ) or - exists( - DataFlowPublic::CallNode call, SummaryComponent input, DataFlowPublic::ContentSet contents, - SummaryComponent output - | + exists(DataFlowPublic::CallNode call, SummaryComponent input, SummaryComponent output | summarizableCall(call.asExpr().getExpr(), // SummaryComponentStack::push(SummaryComponent::content(contents), SummaryComponentStack::singleton(input)), // SummaryComponentStack::singleton(output)) and nodeFrom = evaluateSummaryComponentLocal(call, input) and - nodeTo = evaluateSummaryComponentLocal(call, output) and - content = contents.getAReadContent() + nodeTo = evaluateSummaryComponentLocal(call, output) ) } From b13b2ce31902fd786647cb520a048d6899c06770 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 1 Sep 2022 12:32:30 +0200 Subject: [PATCH 11/39] Ruby: fix join order when building append relation --- .../codeql/ruby/typetracking/TypeTracker.qll | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index dd3fb6495ee..b05bdfd77a5 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -16,6 +16,11 @@ private module Cached { LoadStep(TypeTrackerContentSet contents) 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) { @@ -28,21 +33,24 @@ private module Cached { or step = ReturnStep() and hasCall = false and result = tt or - exists(TypeTrackerContentSet contents | - step = LoadStep(contents) and - currentContent = contents.getAReadContent() and - result = MkTypeTracker(hasCall, noContent()) - ) - or - exists(TypeTrackerContentSet contents | - step = StoreStep(contents) and - currentContent = noContent() and - result = MkTypeTracker(hasCall, contents.getAStoreContent()) - ) - or step = JumpStep() and result = MkTypeTracker(false, currentContent) ) + or + exists(TypeTrackerContentSet contents, boolean hasCall | + step = LoadStep(pragma[only_bind_into](contents)) and + tt = MkTypeTracker(hasCall, contents.getAReadContent()) and + result = noContentTypeTracker(hasCall) + or + step = StoreStep(pragma[only_bind_into](contents)) and + tt = noContentTypeTracker(hasCall) and + result = MkTypeTracker(hasCall, contents.getAStoreContent()) + ) + } + + pragma[nomagic] + private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) { + result = MkTypeBackTracker(hasReturn, noContent()) } /** Gets the summary resulting from prepending `step` to this type-tracking summary. */ @@ -57,21 +65,19 @@ private module Cached { or step = ReturnStep() and result = MkTypeBackTracker(true, content) or - exists(TypeTrackerContentSet contents | - step = LoadStep(contents) and - content = noContent() and - result = MkTypeBackTracker(hasReturn, contents.getAStoreContent()) - ) - or - exists(TypeTrackerContentSet contents | - step = StoreStep(contents) and - content = contents.getAReadContent() and - result = MkTypeBackTracker(hasReturn, noContent()) - ) - or step = JumpStep() and result = MkTypeBackTracker(false, content) ) + or + exists(TypeTrackerContentSet contents, boolean hasReturn | + step = StoreStep(pragma[only_bind_into](contents)) and + tbt = MkTypeBackTracker(hasReturn, contents.getAReadContent()) and + result = noContentTypeBackTracker(hasReturn) + or + step = LoadStep(pragma[only_bind_into](contents)) and + tbt = noContentTypeBackTracker(hasReturn) and + result = MkTypeBackTracker(hasReturn, contents.getAStoreContent()) + ) } /** From cbf16579ed6bb0902ce715ce2316d88bb47c6d94 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 5 Sep 2022 08:08:18 +0200 Subject: [PATCH 12/39] Ruby: tweak pipeline a bit --- .../ruby/typetracking/TypeTrackerSpecific.qll | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 6ce18c27d4e..e35bef8ee59 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -155,11 +155,12 @@ predicate returnStep(Node nodeFrom, Node nodeTo) { predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContentSet contents) { postUpdateStoreStep(nodeFrom, nodeTo, contents) or - exists(DataFlowPublic::CallNode call, SummaryComponent input, SummaryComponent output | - summarizableCall(call.asExpr().getExpr(), // - SummaryComponentStack::singleton(input), - SummaryComponentStack::push(SummaryComponent::content(contents), - SummaryComponentStack::singleton(output))) and + 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) ) @@ -194,11 +195,12 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContentSet conten nodeTo.asExpr() = call ) or - exists(DataFlowPublic::CallNode call, SummaryComponent input, SummaryComponent output | - summarizableCall(call.asExpr().getExpr(), // - SummaryComponentStack::push(SummaryComponent::content(contents), - SummaryComponentStack::singleton(input)), // - SummaryComponentStack::singleton(output)) and + 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) ) @@ -211,14 +213,24 @@ class Boolean extends boolean { Boolean() { this = true or this = false } } -/** Holds if `call` has a summary consisting of the given `input`/`output` pair. */ -private predicate summarizableCall( - MethodCall call, SummaryComponentStack input, SummaryComponentStack output +private import SummaryComponentStack + +private predicate hasStoreSummary( + SummarizedCallable callable, TypeTrackerContentSet contents, SummaryComponent input, + SummaryComponent output ) { - exists(SummarizedCallable callable | - call = callable.getACallSimple() and - callable.propagatesFlow(input, output, true) - ) + callable + .propagatesFlow(singleton(input), + push(SummaryComponent::content(contents), singleton(output)), true) +} + +private predicate hasLoadSummary( + SummarizedCallable callable, TypeTrackerContentSet contents, SummaryComponent input, + SummaryComponent output +) { + callable + .propagatesFlow(push(SummaryComponent::content(contents), singleton(input)), + singleton(output), true) } /** From 576e320bf54677f9459dfc85e196dc462de0d9d9 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 12 Sep 2022 09:48:38 +0200 Subject: [PATCH 13/39] Python: sync --- .../dataflow/new/internal/TypeTracker.qll | 74 ++++++++++++------- .../new/internal/TypeTrackerSpecific.qll | 9 +++ 2 files changed, 57 insertions(+), 26 deletions(-) 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 62895a9431e..b05bdfd77a5 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll @@ -12,31 +12,45 @@ private module Cached { LevelStep() or CallStep() or ReturnStep() or - StoreStep(TypeTrackerContent content) or - LoadStep(TypeTrackerContent content) or + StoreStep(TypeTrackerContentSet contents) or + LoadStep(TypeTrackerContentSet contents) 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, OptionalTypeTrackerContent content | - tt = MkTypeTracker(hasCall, content) + exists(Boolean hasCall, OptionalTypeTrackerContent currentContent | + tt = MkTypeTracker(hasCall, currentContent) | step = LevelStep() and result = tt or - step = CallStep() and result = MkTypeTracker(true, content) + step = CallStep() and result = MkTypeTracker(true, currentContent) or step = ReturnStep() and hasCall = false and result = tt or - step = LoadStep(content) and result = MkTypeTracker(hasCall, noContent()) - or - exists(TypeTrackerContent p | - step = StoreStep(p) and content = noContent() and result = MkTypeTracker(hasCall, p) - ) - or step = JumpStep() and - result = MkTypeTracker(false, content) + result = MkTypeTracker(false, currentContent) ) + or + exists(TypeTrackerContentSet contents, boolean hasCall | + step = LoadStep(pragma[only_bind_into](contents)) and + tt = MkTypeTracker(hasCall, contents.getAReadContent()) and + result = noContentTypeTracker(hasCall) + or + step = StoreStep(pragma[only_bind_into](contents)) and + tt = noContentTypeTracker(hasCall) and + result = MkTypeTracker(hasCall, contents.getAStoreContent()) + ) + } + + pragma[nomagic] + private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) { + result = MkTypeBackTracker(hasReturn, noContent()) } /** Gets the summary resulting from prepending `step` to this type-tracking summary. */ @@ -51,15 +65,19 @@ private module Cached { or step = ReturnStep() and result = MkTypeBackTracker(true, content) or - exists(TypeTrackerContent p | - step = LoadStep(p) and content = noContent() and result = MkTypeBackTracker(hasReturn, p) - ) - or - step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, noContent()) - or step = JumpStep() and result = MkTypeBackTracker(false, content) ) + or + exists(TypeTrackerContentSet contents, boolean hasReturn | + step = StoreStep(pragma[only_bind_into](contents)) and + tbt = MkTypeBackTracker(hasReturn, contents.getAReadContent()) and + result = noContentTypeBackTracker(hasReturn) + or + step = LoadStep(pragma[only_bind_into](contents)) and + tbt = noContentTypeBackTracker(hasReturn) and + result = MkTypeBackTracker(hasReturn, contents.getAStoreContent()) + ) } /** @@ -99,9 +117,11 @@ class StepSummary extends TStepSummary { or this instanceof ReturnStep and result = "return" or - exists(TypeTrackerContent content | this = StoreStep(content) | result = "store " + content) + exists(TypeTrackerContentSet contents | this = StoreStep(contents) | + result = "store " + contents + ) or - exists(TypeTrackerContent content | this = LoadStep(content) | result = "load " + content) + exists(TypeTrackerContentSet contents | this = LoadStep(contents) | result = "load " + contents) or this instanceof JumpStep and result = "jump" } @@ -115,11 +135,11 @@ private predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSu levelStep(nodeFrom, nodeTo) and summary = LevelStep() or - exists(TypeTrackerContent content | - StepSummary::localSourceStoreStep(nodeFrom, nodeTo, content) and - summary = StoreStep(content) + exists(TypeTrackerContentSet contents | + StepSummary::localSourceStoreStep(nodeFrom, nodeTo, contents) and + summary = StoreStep(contents) or - basicLoadStep(nodeFrom, nodeTo, content) and summary = LoadStep(content) + basicLoadStep(nodeFrom, nodeTo, contents) and summary = LoadStep(contents) ) } @@ -189,8 +209,10 @@ 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, TypeTrackerContent content) { - exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content)) + predicate localSourceStoreStep( + Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContentSet contents + ) { + exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, contents)) } } 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 b0a8c85c1fc..16cf16449a9 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll @@ -28,6 +28,15 @@ class TypeTrackerContent extends OptionalTypeTrackerContent { /** The content string representing no value. */ OptionalTypeTrackerContent noContent() { result = "" } +/** A content set, which is currently just a singleton set for Python. */ +class TypeTrackerContentSet extends TypeTrackerContent { + /** Gets a content to read from at a load step. */ + TypeTrackerContent getAReadContent() { result = this } + + /** Gets a content to write to at a store step. */ + TypeTrackerContent getAStoreContent() { result = this } +} + predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStepForTypetracking/2; predicate jumpStep = DataFlowPrivate::jumpStepSharedWithTypeTracker/2; From 7737e7542777e74bebc2ebf0b33e9d9ad7e3f3bc Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 8 Sep 2022 09:17:49 +0200 Subject: [PATCH 14/39] Update some QLDoc comments --- .../lib/semmle/python/dataflow/new/internal/TypeTracker.qll | 4 ++-- ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll | 4 ++-- .../ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) 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 b05bdfd77a5..7aab3e1951c 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll @@ -185,7 +185,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 `contents` 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,7 +204,7 @@ module StepSummary { * def bar(x): * z = x.attr * ``` - * for the attribute write `x.attr = y`, we will have `content` being the literal string `"attr"`, + * for the attribute write `x.attr = y`, we will have `contents` being the literal string `"attr"`, * `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the * 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. diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index b05bdfd77a5..7aab3e1951c 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -185,7 +185,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 `contents` 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,7 +204,7 @@ module StepSummary { * def bar(x): * z = x.attr * ``` - * for the attribute write `x.attr = y`, we will have `content` being the literal string `"attr"`, + * for the attribute write `x.attr = y`, we will have `contents` being the literal string `"attr"`, * `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the * 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. diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index e35bef8ee59..c4192c3e2be 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -122,11 +122,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. @@ -145,7 +145,7 @@ 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 From e47deaffbf6c58dd9c5c3db1e61b8d744c2ee140 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 13 Sep 2022 10:56:18 +0200 Subject: [PATCH 15/39] Ruby: More QLDoc police --- .../python/dataflow/new/internal/TypeTrackerSpecific.qll | 2 +- ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 16cf16449a9..e8af3ce975e 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll @@ -25,7 +25,7 @@ class TypeTrackerContent extends OptionalTypeTrackerContent { TypeTrackerContent() { this != "" } } -/** The content string representing no value. */ +/** Gets the content string representing no value. */ OptionalTypeTrackerContent noContent() { result = "" } /** A content set, which is currently just a singleton set for Python. */ diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index c4192c3e2be..b6f02ed3c33 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -167,8 +167,8 @@ predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContentSet conte } /** - * A `content`-store step from `nodeFrom -> nodeTo` where the destination node is a post-update - * node that should be treated as a local source node. + * 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, TypeTrackerContentSet contents) { // TODO: support SetterMethodCall inside TuplePattern From a5ed3d791b17879a943d235f38379feab8a862d9 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 15 Sep 2022 13:42:25 +0200 Subject: [PATCH 16/39] Ruby: expand test case to reveal mismatching forward/backward flow --- .../type-tracker/TypeTracker.expected | 193 ++++++++++++++++-- .../dataflow/type-tracker/TypeTracker.ql | 24 +++ .../dataflow/type-tracker/type_tracker.rb | 10 +- 3 files changed, 214 insertions(+), 13 deletions(-) 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 e19ca057c33..d77ff00e405 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected @@ -147,25 +147,94 @@ 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:37:3 | &block | type tracker without call steps | type_tracker.rb:34:1:37:3 | &block | -| type_tracker.rb:34:1:37:3 | return return in throughArray | type tracker without call steps | type_tracker.rb:34:1:37:3 | return return in throughArray | -| type_tracker.rb:34:1:37:3 | self in throughArray | type tracker without call steps | type_tracker.rb:34:1:37:3 | self in throughArray | -| type_tracker.rb:34:1:37:3 | throughArray | type tracker without call steps | type_tracker.rb:34:1:37:3 | throughArray | +| 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 without call steps | type_tracker.rb:34:1:37:3 | return return in throughArray | +| 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:34:1:37:3 | return return in throughArray | | 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) | @@ -377,11 +446,11 @@ trackEnd | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:26:10:26:11 | p1 | | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:32:26:32:26 | 8 | -| type_tracker.rb:34:1:37:3 | &block | type_tracker.rb:34:1:37:3 | &block | -| type_tracker.rb:34:1:37:3 | return return in throughArray | type_tracker.rb:34:1:37:3 | return return in throughArray | -| type_tracker.rb:34:1:37:3 | self in throughArray | type_tracker.rb:34:1:37:3 | self in throughArray | -| type_tracker.rb:34:1:37:3 | throughArray | type_tracker.rb:34:1:37:3 | throughArray | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:37:3 | return return in throughArray | +| 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 | @@ -390,12 +459,112 @@ trackEnd | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:12 | __synth__0 | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:12 | __synth__0 | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:18 | ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:5:39:18 | ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:39:16:39:18 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:40:5:40:12 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:40:5:40:12 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:13 | __synth__0 | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:13 | __synth__0 | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:19 | ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:5:43:19 | ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] | +| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | +| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | +| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | +| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | +| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y | +| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y | +| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y | +| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y | | type_tracker.rb:35:5:35:7 | tmp | type_tracker.rb:35:5:35:7 | tmp | | type_tracker.rb:35:11:35:15 | Array | type_tracker.rb:35:11:35:15 | Array | | type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:5:35:15 | ... = ... | | type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:5:35:15 | ... = ... | | type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:11:35:15 | call to [] | | type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:36:5:36:7 | tmp | -| type_tracker.rb:36:5:36:10 | ...[...] | type_tracker.rb:34:1:37:3 | return return in throughArray | | type_tracker.rb:36:5:36:10 | ...[...] | type_tracker.rb:36:5:36:10 | ...[...] | | type_tracker.rb:36:9:36:9 | 0 | type_tracker.rb:36:9:36:9 | 0 | +| type_tracker.rb:38:5:38:9 | array | type_tracker.rb:38:5:38:9 | array | +| type_tracker.rb:38:13:38:25 | Array | type_tracker.rb:38:13:38:25 | Array | +| type_tracker.rb:38:13:38:25 | call to [] | type_tracker.rb:38:5:38:25 | ... = ... | +| type_tracker.rb:38:13:38:25 | call to [] | type_tracker.rb:38:5:38:25 | ... = ... | +| type_tracker.rb:38:13:38:25 | call to [] | type_tracker.rb:38:13:38:25 | call to [] | +| type_tracker.rb:38:13:38:25 | call to [] | type_tracker.rb:39:5:39:9 | array | +| type_tracker.rb:38:13:38:25 | call to [] | type_tracker.rb:40:5:40:9 | array | +| type_tracker.rb:38:14:38:14 | 1 | type_tracker.rb:38:14:38:14 | 1 | +| type_tracker.rb:38:14:38:14 | 1 | type_tracker.rb:40:5:40:12 | ...[...] | +| type_tracker.rb:38:16:38:16 | 2 | type_tracker.rb:38:16:38:16 | 2 | +| type_tracker.rb:38:18:38:18 | 3 | type_tracker.rb:38:18:38:18 | 3 | +| type_tracker.rb:38:20:38:20 | 4 | type_tracker.rb:38:20:38:20 | 4 | +| type_tracker.rb:38:22:38:22 | 5 | type_tracker.rb:38:22:38:22 | 5 | +| type_tracker.rb:38:24:38:24 | 6 | type_tracker.rb:38:24:38:24 | 6 | +| type_tracker.rb:39:5:39:9 | [post] array | type_tracker.rb:39:5:39:9 | [post] array | +| type_tracker.rb:39:5:39:9 | [post] array | type_tracker.rb:40:5:40:9 | array | +| type_tracker.rb:39:5:39:12 | call to []= | type_tracker.rb:39:5:39:12 | call to []= | +| type_tracker.rb:39:16:39:18 | __synth__0 | type_tracker.rb:39:16:39:18 | __synth__0 | +| type_tracker.rb:40:5:40:12 | ...[...] | type_tracker.rb:40:5:40:12 | ...[...] | +| type_tracker.rb:40:11:40:11 | 0 | type_tracker.rb:40:11:40:11 | 0 | +| type_tracker.rb:42:5:42:10 | array2 | type_tracker.rb:42:5:42:10 | array2 | +| type_tracker.rb:42:14:42:26 | Array | type_tracker.rb:42:14:42:26 | Array | +| type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:42:5:42:26 | ... = ... | +| type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:42:5:42:26 | ... = ... | +| type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:42:14:42:26 | call to [] | +| type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:43:5:43:10 | array2 | +| type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:44:5:44:10 | array2 | +| type_tracker.rb:42:15:42:15 | 1 | type_tracker.rb: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:10 | [post] array2 | type_tracker.rb:44:5:44:10 | array2 | +| type_tracker.rb:43:5:43:13 | call to []= | type_tracker.rb:43:5:43:13 | call to []= | +| type_tracker.rb:43:12:43:12 | 0 | type_tracker.rb:43:12:43:12 | 0 | +| type_tracker.rb:43:17:43:19 | __synth__0 | type_tracker.rb:43:17:43:19 | __synth__0 | +| type_tracker.rb:44:5:44:13 | ...[...] | type_tracker.rb:34:1:45:3 | return return in throughArray | +| type_tracker.rb:44:5:44:13 | ...[...] | type_tracker.rb:44:5:44:13 | ...[...] | +forwardButNoBackwardFlow +| 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:44:5:44:13 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] | +| 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: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: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: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: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: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:44:5:44:13 | ...[...] | +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 f8911840ba8..a14a33f5628 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.ql +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.ql @@ -19,3 +19,27 @@ query predicate trackEnd(LocalSourceNode src, DataFlow::Node dst) { end.flowsTo(dst) ) } + +predicate backtrack(LocalSourceNode sink, TypeBackTracker t, LocalSourceNode src) { + t.start() and + sink = src + or + exists(TypeBackTracker t2, LocalSourceNode mid | + backtrack(sink, t2, mid) and + src = mid.backtrack(t2, t) + ) +} + +predicate backtrackEnd(LocalSourceNode sink, LocalSourceNode src) { + backtrack(sink, TypeBackTracker::end(), src) +} + +query predicate forwardButNoBackwardFlow(LocalSourceNode src, LocalSourceNode sink) { + trackEnd(src, sink) and + not backtrackEnd(sink, src) +} + +query predicate backwardButNoForwardFlow(LocalSourceNode src, LocalSourceNode sink) { + backtrackEnd(sink, src) and + not trackEnd(src, sink) +} 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 ad742366a5c..9bdcda38316 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 @@ -31,7 +31,15 @@ keyword(p1: 3, p2: 4) keyword(p2: 5, p1: 6) keyword(:p2 => 7, :p1 => 8) -def throughArray(obj) +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 From 85d0c63ec76c668cd903af9e476f038d9042ee10 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 15 Sep 2022 14:18:57 +0200 Subject: [PATCH 17/39] Ruby: store a ContentSet on type tracker instances --- .../dataflow/internal/DataFlowPrivate.qll | 8 +- .../ruby/dataflow/internal/DataFlowPublic.qll | 15 ++- .../codeql/ruby/typetracking/TypeTracker.qll | 93 ++++++++++--------- .../ruby/typetracking/TypeTrackerSpecific.qll | 5 +- .../type-tracker/TypeTracker.expected | 15 --- 5 files changed, 72 insertions(+), 64 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index 6f43190705f..22c1aa812cf 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -378,12 +378,16 @@ private module Cached { } 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 TOptionalContent = diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll index d412967306e..c955d2eb5ec 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -347,13 +347,23 @@ module Content { NoContent noContent() { any() } } +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 } +} + /** * An entity that represents a set of `Content`s. * * 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) } @@ -366,8 +376,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/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index 7aab3e1951c..44f4bb7d5d0 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -18,65 +18,71 @@ private module Cached { pragma[nomagic] private TypeTracker noContentTypeTracker(boolean hasCall) { - result = MkTypeTracker(hasCall, noContent()) + result = MkTypeTracker(hasCall, noContentSet()) } /** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */ cached TypeTracker append(TypeTracker tt, StepSummary step) { - exists(Boolean hasCall, OptionalTypeTrackerContent currentContent | - tt = MkTypeTracker(hasCall, currentContent) + exists(Boolean hasCall, OptionalTypeTrackerContentSet currentContents | + tt = MkTypeTracker(hasCall, currentContents) | step = LevelStep() and result = tt or - step = CallStep() and result = MkTypeTracker(true, currentContent) + step = CallStep() and result = MkTypeTracker(true, currentContents) or step = ReturnStep() and hasCall = false and result = tt or step = JumpStep() and - result = MkTypeTracker(false, currentContent) + result = MkTypeTracker(false, currentContents) ) or - exists(TypeTrackerContentSet contents, boolean hasCall | - step = LoadStep(pragma[only_bind_into](contents)) and - tt = MkTypeTracker(hasCall, contents.getAReadContent()) and - result = noContentTypeTracker(hasCall) + exists(TypeTrackerContentSet storeContents, boolean hasCall | + exists(TypeTrackerContentSet loadContents | + step = LoadStep(pragma[only_bind_into](loadContents)) and + tt = MkTypeTracker(hasCall, storeContents) and + storeContents.getAStoreContent() = loadContents.getAReadContent() and + result = noContentTypeTracker(hasCall) + ) or - step = StoreStep(pragma[only_bind_into](contents)) and + step = StoreStep(pragma[only_bind_into](storeContents)) and tt = noContentTypeTracker(hasCall) and - result = MkTypeTracker(hasCall, contents.getAStoreContent()) + result = MkTypeTracker(hasCall, storeContents) ) } pragma[nomagic] private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) { - result = MkTypeBackTracker(hasReturn, noContent()) + result = MkTypeBackTracker(hasReturn, noContentSet()) } /** Gets the summary resulting from prepending `step` to this type-tracking summary. */ cached TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) { - exists(Boolean hasReturn, OptionalTypeTrackerContent content | - tbt = MkTypeBackTracker(hasReturn, content) + exists(Boolean hasReturn, OptionalTypeTrackerContentSet contents | + tbt = MkTypeBackTracker(hasReturn, contents) | step = LevelStep() and result = tbt or step = CallStep() and hasReturn = false and result = tbt or - step = ReturnStep() and result = MkTypeBackTracker(true, content) + step = ReturnStep() and result = MkTypeBackTracker(true, contents) or step = JumpStep() and - result = MkTypeBackTracker(false, content) + result = MkTypeBackTracker(false, contents) ) or - exists(TypeTrackerContentSet contents, boolean hasReturn | - step = StoreStep(pragma[only_bind_into](contents)) and - tbt = MkTypeBackTracker(hasReturn, contents.getAReadContent()) and - result = noContentTypeBackTracker(hasReturn) + exists(TypeTrackerContentSet loadContents, boolean hasReturn | + exists(TypeTrackerContentSet storeContents | + step = StoreStep(pragma[only_bind_into](storeContents)) and + tbt = MkTypeBackTracker(hasReturn, loadContents) and + storeContents.getAStoreContent() = loadContents.getAReadContent() and + result = noContentTypeBackTracker(hasReturn) + ) or - step = LoadStep(pragma[only_bind_into](contents)) and + step = LoadStep(pragma[only_bind_into](loadContents)) and tbt = noContentTypeBackTracker(hasReturn) and - result = MkTypeBackTracker(hasReturn, contents.getAStoreContent()) + result = MkTypeBackTracker(hasReturn, loadContents) ) } @@ -216,7 +222,8 @@ module StepSummary { } } -private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) +private newtype TTypeTracker = + MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContentSet contents) /** * A summary of the steps needed to track a value to a given dataflow node. @@ -247,9 +254,9 @@ private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalTypeTracke */ class TypeTracker extends TTypeTracker { Boolean hasCall; - OptionalTypeTrackerContent content; + OptionalTypeTrackerContentSet contents; - TypeTracker() { this = MkTypeTracker(hasCall, content) } + TypeTracker() { this = MkTypeTracker(hasCall, contents) } /** Gets the summary resulting from appending `step` to this type-tracking summary. */ TypeTracker append(StepSummary step) { result = append(this, step) } @@ -259,8 +266,8 @@ class TypeTracker extends TTypeTracker { exists(string withCall, string withContent | (if hasCall = true then withCall = "with" else withCall = "without") and ( - if content != noContent() - then withContent = " with content " + content + if contents != noContentSet() + then withContent = " with content " + contents else withContent = "" ) and result = "type tracker " + withCall + " call steps" + withContent @@ -270,26 +277,26 @@ class TypeTracker extends TTypeTracker { /** * Holds if this is the starting point of type tracking. */ - predicate start() { hasCall = false and content = noContent() } + predicate start() { hasCall = false and contents = noContentSet() } /** * 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(TypeTrackerContent contentName) { - hasCall = false and content = contentName + predicate startInContent(TypeTrackerContentSet contentName) { + hasCall = false and contents = 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 = noContent() } + predicate call() { hasCall = true and contents = noContentSet() } /** * Holds if this is the end point of type tracking. */ - predicate end() { content = noContent() } + predicate end() { contents = noContentSet() } /** * INTERNAL. DO NOT USE. @@ -303,7 +310,7 @@ class TypeTracker extends TTypeTracker { * * Gets the content associated with this type tracker. */ - OptionalTypeTrackerContent getContent() { result = content } + OptionalTypeTrackerContentSet getContent() { result = contents } /** * Gets a type tracker that starts where this one has left off to allow continued @@ -311,7 +318,7 @@ class TypeTracker extends TTypeTracker { * * This predicate is only defined if the type is not associated to a piece of content. */ - TypeTracker continue() { content = noContent() and result = this } + TypeTracker continue() { contents = noContentSet() and result = this } /** * Gets the summary that corresponds to having taken a forwards @@ -370,7 +377,7 @@ module TypeTracker { } private newtype TTypeBackTracker = - MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) + MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContentSet contents) /** * A summary of the steps needed to back-track a use of a value to a given dataflow node. @@ -404,9 +411,9 @@ private newtype TTypeBackTracker = */ class TypeBackTracker extends TTypeBackTracker { Boolean hasReturn; - OptionalTypeTrackerContent content; + OptionalTypeTrackerContentSet contents; - TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) } + TypeBackTracker() { this = MkTypeBackTracker(hasReturn, contents) } /** Gets the summary resulting from prepending `step` to this type-tracking summary. */ TypeBackTracker prepend(StepSummary step) { result = prepend(this, step) } @@ -416,8 +423,8 @@ class TypeBackTracker extends TTypeBackTracker { exists(string withReturn, string withContent | (if hasReturn = true then withReturn = "with" else withReturn = "without") and ( - if content != noContent() - then withContent = " with content " + content + if contents != noContentSet() + then withContent = " with content " + contents else withContent = "" ) and result = "type back-tracker " + withReturn + " return steps" + withContent @@ -427,12 +434,12 @@ class TypeBackTracker extends TTypeBackTracker { /** * Holds if this is the starting point of type tracking. */ - predicate start() { hasReturn = false and content = noContent() } + predicate start() { hasReturn = false and contents = noContentSet() } /** * Holds if this is the end point of type tracking. */ - predicate end() { content = noContent() } + predicate end() { contents = noContentSet() } /** * INTERNAL. DO NOT USE. @@ -447,7 +454,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 = noContent() and result = this } + TypeBackTracker continue() { contents = noContentSet() and result = this } /** * Gets the summary that corresponds to having taken a backwards @@ -504,7 +511,7 @@ class TypeBackTracker extends TTypeBackTracker { * also flow to `sink`. */ TypeTracker getACompatibleTypeTracker() { - exists(boolean hasCall | result = MkTypeTracker(hasCall, content) | + exists(boolean hasCall | result = MkTypeTracker(hasCall, contents) | hasCall = false or this.hasReturn() = false ) } diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index b6f02ed3c33..f68ea0d760e 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -24,7 +24,10 @@ class TypeTrackerContent = DataFlowPublic::Content; class TypeTrackerContentSet = DataFlowPublic::ContentSet; -predicate noContent = DataFlowPublic::Content::noContent/0; +class OptionalTypeTrackerContentSet = DataFlowPublic::OptionalContentSet; + +/** Gets the "no content set" value to use for a type tracker not inside any content. */ +OptionalTypeTrackerContentSet noContentSet() { result.isNoContentSet() } /** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2; 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 d77ff00e405..6fc598170de 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected @@ -552,19 +552,4 @@ trackEnd | 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 -| 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:44:5:44:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] | -| 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: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: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: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: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: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:44:5:44:13 | ...[...] | backwardButNoForwardFlow From 6abf77d40d3bf4c079ef48933a4a0853a1409931 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 15 Sep 2022 14:32:53 +0200 Subject: [PATCH 18/39] Factor comparison into compatibleContents --- .../lib/codeql/ruby/typetracking/TypeTracker.qll | 4 ++-- .../ruby/typetracking/TypeTrackerSpecific.qll | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index 44f4bb7d5d0..bc3f9ae465e 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -41,7 +41,7 @@ private module Cached { exists(TypeTrackerContentSet loadContents | step = LoadStep(pragma[only_bind_into](loadContents)) and tt = MkTypeTracker(hasCall, storeContents) and - storeContents.getAStoreContent() = loadContents.getAReadContent() and + compatibleContents(storeContents, loadContents) and result = noContentTypeTracker(hasCall) ) or @@ -76,7 +76,7 @@ private module Cached { exists(TypeTrackerContentSet storeContents | step = StoreStep(pragma[only_bind_into](storeContents)) and tbt = MkTypeBackTracker(hasReturn, loadContents) and - storeContents.getAStoreContent() = loadContents.getAReadContent() and + compatibleContents(storeContents, loadContents) and result = noContentTypeBackTracker(hasReturn) ) or diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index f68ea0d760e..4e151db68a9 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -15,17 +15,18 @@ class Node = DataFlowPublic::Node; class TypeTrackingNode = DataFlowPublic::LocalSourceNode; -private newtype TOptionalTypeTrackerContent = - MkAttribute(string name) { name = any(Ast::SetterMethodCall c).getTargetName() } or - MkContent(DataFlowPublic::Content content) or - MkNoContent() - -class TypeTrackerContent = DataFlowPublic::Content; - class TypeTrackerContentSet = DataFlowPublic::ContentSet; class OptionalTypeTrackerContentSet = DataFlowPublic::OptionalContentSet; +/** + * Holds if a value stored with `storeContents` can be read back with `loadContents`. + */ +pragma[inline] +predicate compatibleContents(TypeTrackerContentSet storeContents, TypeTrackerContentSet loadContents) { + storeContents.getAStoreContent() = loadContents.getAReadContent() +} + /** Gets the "no content set" value to use for a type tracker not inside any content. */ OptionalTypeTrackerContentSet noContentSet() { result.isNoContentSet() } From dd23e125e589aab1712fd867c7c8c143e9ac91bd Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 15 Sep 2022 14:35:08 +0200 Subject: [PATCH 19/39] Rename TypeTrackerContentSet -> TypeTrackerContent --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 4 +- .../codeql/ruby/typetracking/TypeTracker.qll | 93 +++++++++---------- .../ruby/typetracking/TypeTrackerSpecific.qll | 18 ++-- 3 files changed, 55 insertions(+), 60 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index ecf1e4a9c92..7ab280fb628 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -520,7 +520,7 @@ module API { read = c.getExpr() ) or - exists(TypeTrackerSpecific::TypeTrackerContentSet c | + exists(TypeTrackerSpecific::TypeTrackerContent c | TypeTrackerSpecific::basicLoadStep(node, ref, c) and lbl = Label::content(c.getAStoreContent()) ) @@ -532,7 +532,7 @@ module API { * from a def node that is reachable from `node`. */ private predicate defStep(Label::ApiLabel lbl, DataFlow::Node node, DataFlow::Node rhs) { - exists(TypeTrackerSpecific::TypeTrackerContentSet c | + exists(TypeTrackerSpecific::TypeTrackerContent c | TypeTrackerSpecific::basicStoreStep(rhs, node, c) and lbl = Label::content(c.getAStoreContent()) ) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index bc3f9ae465e..d689c900381 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -12,19 +12,19 @@ private module Cached { LevelStep() or CallStep() or ReturnStep() or - StoreStep(TypeTrackerContentSet contents) or - LoadStep(TypeTrackerContentSet contents) or + StoreStep(TypeTrackerContent content) or + LoadStep(TypeTrackerContent content) or JumpStep() pragma[nomagic] private TypeTracker noContentTypeTracker(boolean hasCall) { - result = MkTypeTracker(hasCall, noContentSet()) + 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, OptionalTypeTrackerContentSet currentContents | + exists(Boolean hasCall, OptionalTypeTrackerContent currentContents | tt = MkTypeTracker(hasCall, currentContents) | step = LevelStep() and result = tt @@ -37,8 +37,8 @@ private module Cached { result = MkTypeTracker(false, currentContents) ) or - exists(TypeTrackerContentSet storeContents, boolean hasCall | - exists(TypeTrackerContentSet loadContents | + 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 @@ -53,27 +53,27 @@ private module Cached { pragma[nomagic] private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) { - result = MkTypeBackTracker(hasReturn, noContentSet()) + 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, OptionalTypeTrackerContentSet contents | - tbt = MkTypeBackTracker(hasReturn, contents) + 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, contents) + step = ReturnStep() and result = MkTypeBackTracker(true, content) or step = JumpStep() and - result = MkTypeBackTracker(false, contents) + result = MkTypeBackTracker(false, content) ) or - exists(TypeTrackerContentSet loadContents, boolean hasReturn | - exists(TypeTrackerContentSet storeContents | + 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 @@ -123,11 +123,9 @@ class StepSummary extends TStepSummary { or this instanceof ReturnStep and result = "return" or - exists(TypeTrackerContentSet contents | this = StoreStep(contents) | - result = "store " + contents - ) + exists(TypeTrackerContent content | this = StoreStep(content) | result = "store " + content) or - exists(TypeTrackerContentSet contents | this = LoadStep(contents) | result = "load " + contents) + exists(TypeTrackerContent content | this = LoadStep(content) | result = "load " + content) or this instanceof JumpStep and result = "jump" } @@ -141,11 +139,11 @@ private predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSu levelStep(nodeFrom, nodeTo) and summary = LevelStep() or - exists(TypeTrackerContentSet contents | - StepSummary::localSourceStoreStep(nodeFrom, nodeTo, contents) and - summary = StoreStep(contents) + exists(TypeTrackerContent content | + StepSummary::localSourceStoreStep(nodeFrom, nodeTo, content) and + summary = StoreStep(content) or - basicLoadStep(nodeFrom, nodeTo, contents) and summary = LoadStep(contents) + basicLoadStep(nodeFrom, nodeTo, content) and summary = LoadStep(content) ) } @@ -191,7 +189,7 @@ module StepSummary { } /** - * Holds if `nodeFrom` is being written to the `contents` 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" @@ -210,20 +208,17 @@ module StepSummary { * def bar(x): * z = x.attr * ``` - * for the attribute write `x.attr = y`, we will have `contents` being the literal string `"attr"`, + * for the attribute write `x.attr = y`, we will have `content` being the literal string `"attr"`, * `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the * 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, TypeTrackerContentSet contents - ) { - exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, contents)) + 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, OptionalTypeTrackerContentSet contents) +private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) /** * A summary of the steps needed to track a value to a given dataflow node. @@ -254,9 +249,9 @@ private newtype TTypeTracker = */ class TypeTracker extends TTypeTracker { Boolean hasCall; - OptionalTypeTrackerContentSet contents; + OptionalTypeTrackerContent content; - TypeTracker() { this = MkTypeTracker(hasCall, contents) } + TypeTracker() { this = MkTypeTracker(hasCall, content) } /** Gets the summary resulting from appending `step` to this type-tracking summary. */ TypeTracker append(StepSummary step) { result = append(this, step) } @@ -266,8 +261,8 @@ class TypeTracker extends TTypeTracker { exists(string withCall, string withContent | (if hasCall = true then withCall = "with" else withCall = "without") and ( - if contents != noContentSet() - then withContent = " with content " + contents + if content != noContent() + then withContent = " with content " + content else withContent = "" ) and result = "type tracker " + withCall + " call steps" + withContent @@ -277,26 +272,26 @@ class TypeTracker extends TTypeTracker { /** * Holds if this is the starting point of type tracking. */ - predicate start() { hasCall = false and contents = noContentSet() } + 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(TypeTrackerContentSet contentName) { - hasCall = false and contents = 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 contents = noContentSet() } + predicate call() { hasCall = true and content = noContent() } /** * Holds if this is the end point of type tracking. */ - predicate end() { contents = noContentSet() } + predicate end() { content = noContent() } /** * INTERNAL. DO NOT USE. @@ -310,7 +305,7 @@ class TypeTracker extends TTypeTracker { * * Gets the content associated with this type tracker. */ - OptionalTypeTrackerContentSet getContent() { result = contents } + OptionalTypeTrackerContent getContent() { result = content } /** * Gets a type tracker that starts where this one has left off to allow continued @@ -318,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() { contents = noContentSet() and result = this } + TypeTracker continue() { content = noContent() and result = this } /** * Gets the summary that corresponds to having taken a forwards @@ -377,7 +372,7 @@ module TypeTracker { } private newtype TTypeBackTracker = - MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContentSet contents) + MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) /** * A summary of the steps needed to back-track a use of a value to a given dataflow node. @@ -411,9 +406,9 @@ private newtype TTypeBackTracker = */ class TypeBackTracker extends TTypeBackTracker { Boolean hasReturn; - OptionalTypeTrackerContentSet contents; + OptionalTypeTrackerContent content; - TypeBackTracker() { this = MkTypeBackTracker(hasReturn, contents) } + TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) } /** Gets the summary resulting from prepending `step` to this type-tracking summary. */ TypeBackTracker prepend(StepSummary step) { result = prepend(this, step) } @@ -423,8 +418,8 @@ class TypeBackTracker extends TTypeBackTracker { exists(string withReturn, string withContent | (if hasReturn = true then withReturn = "with" else withReturn = "without") and ( - if contents != noContentSet() - then withContent = " with content " + contents + if content != noContent() + then withContent = " with content " + content else withContent = "" ) and result = "type back-tracker " + withReturn + " return steps" + withContent @@ -434,12 +429,12 @@ class TypeBackTracker extends TTypeBackTracker { /** * Holds if this is the starting point of type tracking. */ - predicate start() { hasReturn = false and contents = noContentSet() } + predicate start() { hasReturn = false and content = noContent() } /** * Holds if this is the end point of type tracking. */ - predicate end() { contents = noContentSet() } + predicate end() { content = noContent() } /** * INTERNAL. DO NOT USE. @@ -454,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() { contents = noContentSet() and result = this } + TypeBackTracker continue() { content = noContent() and result = this } /** * Gets the summary that corresponds to having taken a backwards @@ -511,7 +506,7 @@ class TypeBackTracker extends TTypeBackTracker { * also flow to `sink`. */ TypeTracker getACompatibleTypeTracker() { - exists(boolean hasCall | result = MkTypeTracker(hasCall, contents) | + exists(boolean hasCall | result = MkTypeTracker(hasCall, content) | hasCall = false or this.hasReturn() = false ) } diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 4e151db68a9..9153b836a69 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -15,20 +15,20 @@ class Node = DataFlowPublic::Node; class TypeTrackingNode = DataFlowPublic::LocalSourceNode; -class TypeTrackerContentSet = DataFlowPublic::ContentSet; +class TypeTrackerContent = DataFlowPublic::ContentSet; -class OptionalTypeTrackerContentSet = DataFlowPublic::OptionalContentSet; +class OptionalTypeTrackerContent = DataFlowPublic::OptionalContentSet; /** * Holds if a value stored with `storeContents` can be read back with `loadContents`. */ pragma[inline] -predicate compatibleContents(TypeTrackerContentSet storeContents, TypeTrackerContentSet loadContents) { +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. */ -OptionalTypeTrackerContentSet noContentSet() { result.isNoContentSet() } +OptionalTypeTrackerContent noContent() { result.isNoContentSet() } /** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2; @@ -156,7 +156,7 @@ predicate returnStep(Node nodeFrom, Node nodeTo) { * to `z` inside `bar`, even though this content write happens _after_ `bar` is * called. */ -predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContentSet contents) { +predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent contents) { postUpdateStoreStep(nodeFrom, nodeTo, contents) or exists( @@ -174,7 +174,7 @@ predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContentSet conte * 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, TypeTrackerContentSet contents) { +predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent contents) { // TODO: support SetterMethodCall inside TuplePattern exists(ExprNodes::MethodCallCfgNode call | contents @@ -191,7 +191,7 @@ predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContentSet /** * Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`. */ -predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContentSet contents) { +predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContent contents) { exists(ExprNodes::MethodCallCfgNode call | call.getExpr().getNumberOfArguments() = 0 and contents.isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr().getMethodName())) and @@ -220,7 +220,7 @@ class Boolean extends boolean { private import SummaryComponentStack private predicate hasStoreSummary( - SummarizedCallable callable, TypeTrackerContentSet contents, SummaryComponent input, + SummarizedCallable callable, TypeTrackerContent contents, SummaryComponent input, SummaryComponent output ) { callable @@ -229,7 +229,7 @@ private predicate hasStoreSummary( } private predicate hasLoadSummary( - SummarizedCallable callable, TypeTrackerContentSet contents, SummaryComponent input, + SummarizedCallable callable, TypeTrackerContent contents, SummaryComponent input, SummaryComponent output ) { callable From 9c93ad904f5319161b382713c59fcff9eddce80b Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 15 Sep 2022 14:38:27 +0200 Subject: [PATCH 20/39] Python: sync --- .../dataflow/new/internal/TypeTracker.qll | 66 ++++++++++--------- .../new/internal/TypeTrackerSpecific.qll | 10 +-- 2 files changed, 37 insertions(+), 39 deletions(-) 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 7aab3e1951c..d689c900381 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll @@ -12,8 +12,8 @@ private module Cached { LevelStep() or CallStep() or ReturnStep() or - StoreStep(TypeTrackerContentSet contents) or - LoadStep(TypeTrackerContentSet contents) or + StoreStep(TypeTrackerContent content) or + LoadStep(TypeTrackerContent content) or JumpStep() pragma[nomagic] @@ -24,27 +24,30 @@ private module Cached { /** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */ cached TypeTracker append(TypeTracker tt, StepSummary step) { - exists(Boolean hasCall, OptionalTypeTrackerContent currentContent | - tt = MkTypeTracker(hasCall, currentContent) + exists(Boolean hasCall, OptionalTypeTrackerContent currentContents | + tt = MkTypeTracker(hasCall, currentContents) | step = LevelStep() and result = tt or - step = CallStep() and result = MkTypeTracker(true, currentContent) + step = CallStep() and result = MkTypeTracker(true, currentContents) or step = ReturnStep() and hasCall = false and result = tt or step = JumpStep() and - result = MkTypeTracker(false, currentContent) + result = MkTypeTracker(false, currentContents) ) or - exists(TypeTrackerContentSet contents, boolean hasCall | - step = LoadStep(pragma[only_bind_into](contents)) and - tt = MkTypeTracker(hasCall, contents.getAReadContent()) and - result = noContentTypeTracker(hasCall) + 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](contents)) and + step = StoreStep(pragma[only_bind_into](storeContents)) and tt = noContentTypeTracker(hasCall) and - result = MkTypeTracker(hasCall, contents.getAStoreContent()) + result = MkTypeTracker(hasCall, storeContents) ) } @@ -69,14 +72,17 @@ private module Cached { result = MkTypeBackTracker(false, content) ) or - exists(TypeTrackerContentSet contents, boolean hasReturn | - step = StoreStep(pragma[only_bind_into](contents)) and - tbt = MkTypeBackTracker(hasReturn, contents.getAReadContent()) and - result = noContentTypeBackTracker(hasReturn) + 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](contents)) and + step = LoadStep(pragma[only_bind_into](loadContents)) and tbt = noContentTypeBackTracker(hasReturn) and - result = MkTypeBackTracker(hasReturn, contents.getAStoreContent()) + result = MkTypeBackTracker(hasReturn, loadContents) ) } @@ -117,11 +123,9 @@ class StepSummary extends TStepSummary { or this instanceof ReturnStep and result = "return" or - exists(TypeTrackerContentSet contents | this = StoreStep(contents) | - result = "store " + contents - ) + exists(TypeTrackerContent content | this = StoreStep(content) | result = "store " + content) or - exists(TypeTrackerContentSet contents | this = LoadStep(contents) | result = "load " + contents) + exists(TypeTrackerContent content | this = LoadStep(content) | result = "load " + content) or this instanceof JumpStep and result = "jump" } @@ -135,11 +139,11 @@ private predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSu levelStep(nodeFrom, nodeTo) and summary = LevelStep() or - exists(TypeTrackerContentSet contents | - StepSummary::localSourceStoreStep(nodeFrom, nodeTo, contents) and - summary = StoreStep(contents) + exists(TypeTrackerContent content | + StepSummary::localSourceStoreStep(nodeFrom, nodeTo, content) and + summary = StoreStep(content) or - basicLoadStep(nodeFrom, nodeTo, contents) and summary = LoadStep(contents) + basicLoadStep(nodeFrom, nodeTo, content) and summary = LoadStep(content) ) } @@ -185,7 +189,7 @@ module StepSummary { } /** - * Holds if `nodeFrom` is being written to the `contents` 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,15 +208,13 @@ module StepSummary { * def bar(x): * z = x.attr * ``` - * for the attribute write `x.attr = y`, we will have `contents` being the literal string `"attr"`, + * for the attribute write `x.attr = y`, we will have `content` being the literal string `"attr"`, * `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the * 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, TypeTrackerContentSet contents - ) { - exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, contents)) + predicate localSourceStoreStep(Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContent content) { + exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content)) } } 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 e8af3ce975e..ecf7e4ccca1 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll @@ -28,13 +28,9 @@ class TypeTrackerContent extends OptionalTypeTrackerContent { /** Gets the content string representing no value. */ OptionalTypeTrackerContent noContent() { result = "" } -/** A content set, which is currently just a singleton set for Python. */ -class TypeTrackerContentSet extends TypeTrackerContent { - /** Gets a content to read from at a load step. */ - TypeTrackerContent getAReadContent() { result = this } - - /** Gets a content to write to at a store step. */ - TypeTrackerContent getAStoreContent() { result = this } +pragma[inline] +predicate compatibleContents(TypeTrackerContent storeContent, TypeTrackerContent loadContent) { + storeContent = loadContent } predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStepForTypetracking/2; From 7dfa58b50d46b9fe20e4208483e9567d3d70b0de Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 15 Sep 2022 14:44:35 +0200 Subject: [PATCH 21/39] Remove Content::NoContent --- .../ruby/dataflow/internal/DataFlowPrivate.qll | 8 +------- .../ruby/dataflow/internal/DataFlowPublic.qll | 15 ++------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index 22c1aa812cf..b1b85cf8369 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -390,8 +390,7 @@ private module Cached { class TContentSet = TSingletonContent or TAnyElementContent or TElementLowerBoundContent; cached - newtype TOptionalContent = - TNoContent() or + newtype TContent = TKnownElementContent(ConstantValue cv) { not cv.isInt(_) or cv.getInt() in [0 .. 10] @@ -418,11 +417,6 @@ private module Cached { // Only used by type-tracking TAttributeName(string name) { name = any(AST::SetterMethodCall c).getTargetName() } - cached - class TContent = - TKnownElementContent or TUnknownElementContent or TKnownPairValueContent or - TUnknownPairValueContent or TFieldContent or TAttributeName; - /** * 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 c955d2eb5ec..b2dec6ddd1b 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -235,8 +235,8 @@ predicate localExprFlow(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) { localFlow(exprNode(e1), exprNode(e2)) } -/** A reference contained in an object, or the `noContent()` value. */ -class OptionalContent extends TOptionalContent { +/** A reference contained in an object. */ +class Content extends TContent { /** Gets a textual representation of this content. */ string toString() { none() } @@ -244,9 +244,6 @@ class OptionalContent extends TOptionalContent { Location getLocation() { none() } } -/** A reference contained in an object. */ -class Content extends OptionalContent, TContent { } - /** Provides different sub classes of `Content`. */ module Content { /** An element in a collection, for example an element in an array or in a hash. */ @@ -337,14 +334,6 @@ module Content { /** Gets `AttributeNameContent` of the given name. */ AttributeNameContent getAttributeName(string name) { result.getName() = name } - - /** A value representing no content. */ - class NoContent extends OptionalContent, TNoContent { - override string toString() { result = "noContent()" } - } - - /** Gets the `noContent()` value. */ - NoContent noContent() { any() } } class OptionalContentSet extends TOptionalContentSet { From a7b92295a2031d722c620ca7bf3576815214cf93 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 22 Sep 2022 09:11:49 +0200 Subject: [PATCH 22/39] Ruby: fix a typo --- ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll index bcfba2a16e9..c7e072efe33 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll @@ -38,7 +38,7 @@ module Hash { /** * Gets a call to the method `name` invoked on the `Hash` object - * (not on an hash instance). + * (not on a hash instance). */ private MethodCall getAStaticHashCall(string name) { result.getMethodName() = name and From 588b31d15d943226c620b982cc07df0b38b850c0 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 22 Sep 2022 09:46:05 +0200 Subject: [PATCH 23/39] Ruby: fix another typo --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 7ab280fb628..13dadaf0b65 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -269,7 +269,7 @@ module API { * Gets a node representing the `contents` stored on the base object. */ Node getContents(DataFlow::ContentSet contents) { - // We already use getAStoreContent when generating the graph, and we always use getAReadContent when querying the graph. + // We always use getAStoreContent when generating the graph, and we always use getAReadContent when querying the graph. result = this.getContent(contents.getAReadContent()) } From e09a5e87dd4ef1910adf8a6b868817839a1f0995 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 22 Sep 2022 09:58:18 +0200 Subject: [PATCH 24/39] Ruby: clarify what getAnElement() does --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 13dadaf0b65..8e4ff1b95f4 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -276,7 +276,7 @@ module API { /** 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. */ + /** Gets a node representing an element of this collection (known or unknown). */ Node getAnElement() { result = this.getContents(any(DataFlow::ContentSet set | set.isAnyElement())) } From 032847f33113f1799421d328c82c397ea3f6dd94 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 22 Sep 2022 09:59:05 +0200 Subject: [PATCH 25/39] Ruby: inline getContents --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 1 + 1 file changed, 1 insertion(+) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 8e4ff1b95f4..3975cc6c4bd 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -268,6 +268,7 @@ module API { /** * 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()) From 665ee81967a9a1b54b4fc175e961a627f9e79306 Mon Sep 17 00:00:00 2001 From: Asger F Date: Sun, 4 Sep 2022 11:15:38 +0200 Subject: [PATCH 26/39] Ruby: revert trackUseNode to idiomatic type-tracking The optimizations done here now seem to backfire and cause more problems than they fix. --- ruby/ql/lib/codeql/ruby/ApiGraphs.qll | 37 +++------------------------ 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll index 3975cc6c4bd..3101a34e7b4 100644 --- a/ruby/ql/lib/codeql/ruby/ApiGraphs.qll +++ b/ruby/ql/lib/codeql/ruby/ApiGraphs.qll @@ -580,23 +580,6 @@ 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) @@ -651,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)) } /** From ce3665d50e63dfe9e315ac557492bcad4602b06a Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 22 Sep 2022 14:39:21 +0200 Subject: [PATCH 27/39] Ruby: remove unneeded qualified AST import --- ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index b1b85cf8369..9b85eac68df 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -1,7 +1,6 @@ private import codeql.ruby.AST 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 @@ -415,7 +414,7 @@ private module Cached { ) } or // Only used by type-tracking - TAttributeName(string name) { name = any(AST::SetterMethodCall c).getTargetName() } + TAttributeName(string name) { name = any(SetterMethodCall c).getTargetName() } /** * Holds if `e` is an `ExprNode` that may be returned by a call to `c`. From 14e384aaa2d8ba85b65e5d25c6aef4ea53393abe Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 22 Sep 2022 14:40:20 +0200 Subject: [PATCH 28/39] Ruby: remove unneeded import --- ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.ql | 1 - 1 file changed, 1 deletion(-) 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 b12b5c6eebb..6cf9c0315a5 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.ql +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/ApiGraphs.ql @@ -3,7 +3,6 @@ */ import ruby -import codeql.ruby.DataFlow import codeql.ruby.ApiGraphs query predicate classMethodCalls(API::Node node) { From e1dfed0fcb67046c0b2bf28ac7b85190b68b0539 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 22 Sep 2022 15:07:57 +0200 Subject: [PATCH 29/39] Ruby: move OptionalContentSet to TypeTrackerSpecific.qll --- .../ruby/dataflow/internal/DataFlowPublic.qll | 15 +++------------ .../ruby/typetracking/TypeTrackerSpecific.qll | 12 ++++++++++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll index b2dec6ddd1b..204b6a0ee2a 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPublic.qll @@ -336,23 +336,13 @@ module Content { 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 } -} - /** * An entity that represents a set of `Content`s. * * The set may be interpreted differently depending on whether it is * stored into (`getAStoreContent`) or read from (`getAReadContent`). */ -class ContentSet extends OptionalContentSet, TContentSet { +class ContentSet extends TContentSet { /** Holds if this content set is the singleton `{c}`. */ predicate isSingleton(Content c) { this = TSingletonContent(c) } @@ -365,7 +355,8 @@ class ContentSet extends OptionalContentSet, TContentSet { */ predicate isElementLowerBound(int lower) { this = TElementLowerBoundContent(lower) } - override string toString() { + /** Gets a textual representation of this content set. */ + string toString() { exists(Content c | this.isSingleton(c) and result = c.toString() diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 9153b836a69..0af5d23f111 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -17,7 +17,15 @@ class TypeTrackingNode = DataFlowPublic::LocalSourceNode; class TypeTrackerContent = DataFlowPublic::ContentSet; -class OptionalTypeTrackerContent = DataFlowPublic::OptionalContentSet; +class OptionalTypeTrackerContent extends DataFlowPrivate::TOptionalContentSet { + /** Gets a textual representation of this content set. */ + string toString() { + this instanceof DataFlowPrivate::TNoContentSet and + result = "no content" + or + result = this.(DataFlowPublic::ContentSet).toString() + } +} /** * Holds if a value stored with `storeContents` can be read back with `loadContents`. @@ -28,7 +36,7 @@ predicate compatibleContents(TypeTrackerContent storeContents, TypeTrackerConten } /** Gets the "no content set" value to use for a type tracker not inside any content. */ -OptionalTypeTrackerContent noContent() { result.isNoContentSet() } +OptionalTypeTrackerContent noContent() { result = DataFlowPrivate::TNoContentSet() } /** Holds if there is a simple local flow step from `nodeFrom` to `nodeTo` */ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2; From e56630a485d4b2a4bd2dd2ff252ed02c62d7ea30 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 26 Sep 2022 14:32:05 +0200 Subject: [PATCH 30/39] Ruby: add missing qldoc --- ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 0af5d23f111..ce8a48fcba5 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -17,6 +17,9 @@ class TypeTrackingNode = DataFlowPublic::LocalSourceNode; class TypeTrackerContent = DataFlowPublic::ContentSet; +/** + * An optional content set, that is, a `ContentSet` or the special "no content set" value. + */ class OptionalTypeTrackerContent extends DataFlowPrivate::TOptionalContentSet { /** Gets a textual representation of this content set. */ string toString() { From ce1c25827364d9ba2b7f851ae60b1699a3060a59 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 28 Sep 2022 11:15:25 +0200 Subject: [PATCH 31/39] Ruby: Update TypeTracker.expected --- .../type-tracker/TypeTracker.expected | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) 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 6fc598170de..a0a1065e460 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected @@ -158,8 +158,8 @@ track | 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 with call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] | +| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker 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 | @@ -170,8 +170,8 @@ track | 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:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] | +| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] | | type_tracker.rb:34: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 | @@ -186,17 +186,17 @@ track | 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:14:38:14 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:38:13:38:25 | call to [] | | type_tracker.rb:38:16:38:16 | 2 | type tracker without call steps | type_tracker.rb:38:16:38:16 | 2 | -| type_tracker.rb:38:16:38:16 | 2 | type tracker without call steps with content element 1 | type_tracker.rb:38:13:38:25 | call to [] | +| type_tracker.rb:38:16:38:16 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:38:13:38:25 | call to [] | | type_tracker.rb:38:18:38:18 | 3 | type tracker without call steps | type_tracker.rb:38:18:38:18 | 3 | -| type_tracker.rb:38:18:38:18 | 3 | type tracker without call steps with content element 2 | type_tracker.rb:38:13:38:25 | call to [] | +| type_tracker.rb:38:18:38:18 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:38:13:38:25 | call to [] | | type_tracker.rb:38:20:38:20 | 4 | type tracker without call steps | type_tracker.rb:38:20:38:20 | 4 | -| type_tracker.rb:38:20:38:20 | 4 | type tracker without call steps with content element 3 | type_tracker.rb:38:13:38:25 | call to [] | +| type_tracker.rb:38:20:38:20 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:38:13:38:25 | call to [] | | type_tracker.rb:38:22:38:22 | 5 | type tracker without call steps | type_tracker.rb:38:22:38:22 | 5 | -| type_tracker.rb:38:22:38:22 | 5 | type tracker without call steps with content element 4 | type_tracker.rb:38:13:38:25 | call to [] | +| type_tracker.rb:38:22:38:22 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:38:13:38:25 | call to [] | | type_tracker.rb:38:24:38:24 | 6 | type tracker without call steps | type_tracker.rb:38:24:38:24 | 6 | -| type_tracker.rb:38:24:38:24 | 6 | type tracker without call steps with content element 5 | type_tracker.rb:38:13:38:25 | call to [] | +| type_tracker.rb:38:24:38:24 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:38:13:38:25 | call to [] | | type_tracker.rb:39:5:39:9 | [post] array | type tracker without call steps | type_tracker.rb:39:5:39:9 | [post] array | | type_tracker.rb:39:5:39:12 | call to []= | type tracker without call steps | type_tracker.rb:39:5:39:12 | call to []= | | type_tracker.rb:39:16:39:18 | __synth__0 | type tracker without call steps | type_tracker.rb:39:16:39:18 | __synth__0 | @@ -208,27 +208,27 @@ track | 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:15:42:15 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] | | type_tracker.rb:42:17:42:17 | 2 | type tracker without call steps | type_tracker.rb: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:17:42:17 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:42:14:42:26 | call to [] | | type_tracker.rb:42:19:42:19 | 3 | type tracker without call steps | type_tracker.rb: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:19:42:19 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:42:14:42:26 | call to [] | | type_tracker.rb:42:21:42:21 | 4 | type tracker without call steps | type_tracker.rb: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:21:42:21 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:42:14:42:26 | call to [] | | type_tracker.rb:42:23:42:23 | 5 | type tracker without call steps | type_tracker.rb: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:23:42:23 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:42:14:42:26 | call to [] | | type_tracker.rb:42:25:42:25 | 6 | type tracker without call steps | type_tracker.rb: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:42:25:42:25 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:42:14:42:26 | call to [] | | type_tracker.rb:43:5:43:10 | [post] array2 | type tracker without call steps | type_tracker.rb:43:5:43:10 | [post] array2 | | type_tracker.rb:43:5:43:13 | call to []= | type tracker without call steps | type_tracker.rb:43:5:43:13 | call to []= | | type_tracker.rb:43:12:43:12 | 0 | type tracker without call steps | type_tracker.rb:43:12:43:12 | 0 | From 971657245d6eab911c30e93c9f87c0c7ae3d25f0 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 28 Sep 2022 11:17:13 +0200 Subject: [PATCH 32/39] Ruby: update API graph inline test to match output --- ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6d9df028f02..54c78f25997 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/test1.rb @@ -72,4 +72,4 @@ 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) +A::B::C[0] #$ use=getMember("A").getMember("B").getMember("C").getContent(element_0) From fea47c85f3377655c0dbb713b5445966c3a46958 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 28 Sep 2022 11:40:55 +0200 Subject: [PATCH 33/39] Ruby: expand on type-tracking test a bit --- .../type-tracker/TypeTracker.expected | 184 +++++++++++++++--- .../dataflow/type-tracker/type_tracker.rb | 10 +- 2 files changed, 169 insertions(+), 25 deletions(-) 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 a0a1065e460..3105ebda6b1 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected @@ -147,35 +147,49 @@ 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:1:53:3 | &block | type tracker without call steps | type_tracker.rb:34:1:53:3 | &block | +| type_tracker.rb:34:1:53:3 | return return in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:34:1:53:3 | self in throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | self in throughArray | +| type_tracker.rb:34:1:53:3 | throughArray | type tracker without call steps | type_tracker.rb:34:1:53:3 | throughArray | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:36:5:36:10 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:40:5:40:12 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:44:5:44:13 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:52:5:52:13 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content 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 attribute [] | type_tracker.rb:47:5:47:10 | [post] array3 | +| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content attribute [] | type_tracker.rb:51:5:51:10 | [post] array4 | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:38:13:38:25 | call to [] | +| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:50:14:50:26 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:45:3 | return return in throughArray | +| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] | +| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content 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 attribute [] | type_tracker.rb:47:5:47:10 | [post] array3 | +| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content attribute [] | type_tracker.rb:51:5:51:10 | [post] array4 | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:38:13:38:25 | call to [] | +| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:50:14:50:26 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:42:14:42:26 | call to [] | +| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] | | type_tracker.rb:34:23:34:23 | y | type tracker with call steps | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type tracker without call steps | type_tracker.rb:34:23:34:23 | y | +| type_tracker.rb:34:26:34:26 | z | type tracker with call steps | type_tracker.rb:34:26:34:26 | z | +| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z | +| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z | +| type_tracker.rb:34:26:34:26 | z | type tracker without call steps | type_tracker.rb:34:26:34:26 | z | | type_tracker.rb:35:5:35:7 | tmp | type tracker without call steps | type_tracker.rb:35:5:35:7 | tmp | | type_tracker.rb:35:11:35:15 | Array | type tracker without call steps | type_tracker.rb:35:11:35:15 | Array | | type_tracker.rb:35:11:35:15 | call to [] | type tracker without call steps | type_tracker.rb:35:11:35:15 | call to [] | @@ -205,27 +219,21 @@ track | 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 or unknown | type_tracker.rb:42:14:42:26 | call to [] | -| type_tracker.rb:42:17:42:17 | 2 | type tracker without call steps | type_tracker.rb: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 or unknown | type_tracker.rb:42:14:42:26 | call to [] | -| type_tracker.rb:42:19:42:19 | 3 | type tracker without call steps | type_tracker.rb: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 or unknown | type_tracker.rb:42:14:42:26 | call to [] | -| type_tracker.rb:42:21:42:21 | 4 | type tracker without call steps | type_tracker.rb: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 or unknown | type_tracker.rb:42:14:42:26 | call to [] | -| type_tracker.rb:42:23:42:23 | 5 | type tracker without call steps | type_tracker.rb: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 or unknown | type_tracker.rb:42:14:42:26 | call to [] | -| type_tracker.rb:42:25:42:25 | 6 | type tracker without call steps | type_tracker.rb: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 or unknown | type_tracker.rb:42:14:42:26 | call to [] | @@ -233,8 +241,61 @@ track | 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 | ...[...] | +| type_tracker.rb:46:5:46:10 | array3 | type tracker without call steps | type_tracker.rb:46:5:46:10 | array3 | +| type_tracker.rb:46:14:46:26 | Array | type tracker without call steps | type_tracker.rb:46:14:46:26 | Array | +| type_tracker.rb:46:14:46:26 | call to [] | type tracker without call steps | type_tracker.rb:46:14:46:26 | call to [] | +| type_tracker.rb:46:15:46:15 | 1 | type tracker without call steps | type_tracker.rb:46:15:46:15 | 1 | +| type_tracker.rb:46:15:46:15 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:46:14:46:26 | call to [] | +| type_tracker.rb:46:17:46:17 | 2 | type tracker without call steps | type_tracker.rb:46:17:46:17 | 2 | +| type_tracker.rb:46:17:46:17 | 2 | type tracker without call steps | type_tracker.rb:48:5:48:13 | ...[...] | +| type_tracker.rb:46:17:46:17 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:46:14:46:26 | call to [] | +| type_tracker.rb:46:19:46:19 | 3 | type tracker without call steps | type_tracker.rb:46:19:46:19 | 3 | +| type_tracker.rb:46:19:46:19 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:46:14:46:26 | call to [] | +| type_tracker.rb:46:21:46:21 | 4 | type tracker without call steps | type_tracker.rb:46:21:46:21 | 4 | +| type_tracker.rb:46:21:46:21 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:46:14:46:26 | call to [] | +| type_tracker.rb:46:23:46:23 | 5 | type tracker without call steps | type_tracker.rb:46:23:46:23 | 5 | +| type_tracker.rb:46:23:46:23 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:46:14:46:26 | call to [] | +| type_tracker.rb:46:25:46:25 | 6 | type tracker without call steps | type_tracker.rb:46:25:46:25 | 6 | +| type_tracker.rb:46:25:46:25 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:46:14:46:26 | call to [] | +| type_tracker.rb:47:5:47:10 | [post] array3 | type tracker without call steps | type_tracker.rb:47:5:47:10 | [post] array3 | +| type_tracker.rb:47:5:47:13 | call to []= | type tracker without call steps | type_tracker.rb:47:5:47:13 | call to []= | +| type_tracker.rb:47:12:47:12 | 0 | type tracker without call steps | type_tracker.rb:47:12:47:12 | 0 | +| type_tracker.rb:47:17:47:19 | __synth__0 | type tracker without call steps | type_tracker.rb:47:17:47:19 | __synth__0 | +| type_tracker.rb:48:5:48:13 | ...[...] | type tracker without call steps | type_tracker.rb:48:5:48:13 | ...[...] | +| type_tracker.rb:48:12:48:12 | 1 | type tracker without call steps | type_tracker.rb:48:12:48:12 | 1 | +| type_tracker.rb:50:5:50:10 | array4 | type tracker without call steps | type_tracker.rb:50:5:50:10 | array4 | +| type_tracker.rb:50:14:50:26 | Array | type tracker without call steps | type_tracker.rb:50:14:50:26 | Array | +| type_tracker.rb:50:14:50:26 | call to [] | type tracker without call steps | type_tracker.rb:50:14:50:26 | call to [] | +| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:50:15:50:15 | 1 | +| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:15:50:15 | 1 | type tracker without call steps with content element 0 or unknown | type_tracker.rb:50:14:50:26 | call to [] | +| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:50:17:50:17 | 2 | +| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:17:50:17 | 2 | type tracker without call steps with content element 1 or unknown | type_tracker.rb:50:14:50:26 | call to [] | +| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:50:19:50:19 | 3 | +| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:19:50:19 | 3 | type tracker without call steps with content element 2 or unknown | type_tracker.rb:50:14:50:26 | call to [] | +| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:50:21:50:21 | 4 | +| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:21:50:21 | 4 | type tracker without call steps with content element 3 or unknown | type_tracker.rb:50:14:50:26 | call to [] | +| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:50:23:50:23 | 5 | +| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:23:50:23 | 5 | type tracker without call steps with content element 4 or unknown | type_tracker.rb:50:14:50:26 | call to [] | +| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:50:25:50:25 | 6 | +| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:25:50:25 | 6 | type tracker without call steps with content element 5 or unknown | type_tracker.rb:50:14:50:26 | call to [] | +| type_tracker.rb:51:5:51:10 | [post] array4 | type tracker without call steps | type_tracker.rb:51:5:51:10 | [post] array4 | +| type_tracker.rb:51:5:51:13 | call to []= | type tracker without call steps | type_tracker.rb:51:5:51:13 | call to []= | +| type_tracker.rb:51:17:51:19 | __synth__0 | type tracker without call steps | type_tracker.rb:51:17:51:19 | __synth__0 | +| type_tracker.rb:52:5:52:13 | ...[...] | type tracker without call steps | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:52:5:52:13 | ...[...] | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | trackEnd | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:18:1:21:3 | self (positional) | @@ -446,11 +507,11 @@ trackEnd | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:25:13:25:14 | p1 | | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:26:10:26:11 | p1 | | type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:32:26:32:26 | 8 | -| type_tracker.rb:34:1: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:1:53:3 | &block | type_tracker.rb:34:1:53:3 | &block | +| type_tracker.rb:34:1:53:3 | return return in throughArray | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:34:1:53:3 | self in throughArray | type_tracker.rb:34:1:53:3 | self in throughArray | +| type_tracker.rb:34:1:53:3 | throughArray | type_tracker.rb:34:1:53:3 | throughArray | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:53:3 | return return in throughArray | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj | @@ -483,6 +544,28 @@ trackEnd | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:43:17:43:19 | obj | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:44:5:44:13 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:13 | __synth__0 | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:13 | __synth__0 | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:19 | ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:5:47:19 | ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:47:17:47:19 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:13 | __synth__0 | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:13 | __synth__0 | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:19 | ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:5:51:19 | ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | ... = ... | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:51:17:51:19 | obj | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:52:5:52:13 | ...[...] | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:34:23:34:23 | y | @@ -491,6 +574,14 @@ trackEnd | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:39:11:39:11 | y | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y | | type_tracker.rb:34:23:34:23 | y | type_tracker.rb:44:12:44:12 | y | +| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:51:12:51:12 | y | +| type_tracker.rb:34:23:34:23 | y | type_tracker.rb:51:12:51:12 | y | +| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z | +| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z | +| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z | +| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:34:26:34:26 | z | +| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:52:12:52:12 | z | +| type_tracker.rb:34:26:34:26 | z | type_tracker.rb:52:12:52:12 | z | | type_tracker.rb:35:5:35:7 | tmp | type_tracker.rb:35:5:35:7 | tmp | | type_tracker.rb:35:11:35:15 | Array | type_tracker.rb:35:11:35:15 | Array | | type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:5:35:15 | ... = ... | @@ -526,22 +617,16 @@ trackEnd | type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:42:14:42:26 | call to [] | | type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:43:5:43:10 | array2 | | type_tracker.rb:42:14:42:26 | call to [] | type_tracker.rb:44:5:44:10 | array2 | -| type_tracker.rb:42:15:42:15 | 1 | type_tracker.rb: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 | @@ -549,7 +634,58 @@ trackEnd | 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 | ...[...] | +| type_tracker.rb:46:5:46:10 | array3 | type_tracker.rb:46:5:46:10 | array3 | +| type_tracker.rb:46:14:46:26 | Array | type_tracker.rb:46:14:46:26 | Array | +| type_tracker.rb:46:14:46:26 | call to [] | type_tracker.rb:46:5:46:26 | ... = ... | +| type_tracker.rb:46:14:46:26 | call to [] | type_tracker.rb:46:5:46:26 | ... = ... | +| type_tracker.rb:46:14:46:26 | call to [] | type_tracker.rb:46:14:46:26 | call to [] | +| type_tracker.rb:46:14:46:26 | call to [] | type_tracker.rb:47:5:47:10 | array3 | +| type_tracker.rb:46:14:46:26 | call to [] | type_tracker.rb:48:5:48:10 | array3 | +| type_tracker.rb:46:15:46:15 | 1 | type_tracker.rb:46:15:46:15 | 1 | +| type_tracker.rb:46:17:46:17 | 2 | type_tracker.rb:46:17:46:17 | 2 | +| type_tracker.rb:46:17:46:17 | 2 | type_tracker.rb:48:5:48:13 | ...[...] | +| type_tracker.rb:46:19:46:19 | 3 | type_tracker.rb:46:19:46:19 | 3 | +| type_tracker.rb:46:21:46:21 | 4 | type_tracker.rb:46:21:46:21 | 4 | +| type_tracker.rb:46:23:46:23 | 5 | type_tracker.rb:46:23:46:23 | 5 | +| type_tracker.rb:46:25:46:25 | 6 | type_tracker.rb:46:25:46:25 | 6 | +| type_tracker.rb:47:5:47:10 | [post] array3 | type_tracker.rb:47:5:47:10 | [post] array3 | +| type_tracker.rb:47:5:47:10 | [post] array3 | type_tracker.rb:48:5:48:10 | array3 | +| type_tracker.rb:47:5:47:13 | call to []= | type_tracker.rb:47:5:47:13 | call to []= | +| type_tracker.rb:47:12:47:12 | 0 | type_tracker.rb:47:12:47:12 | 0 | +| type_tracker.rb:47:17:47:19 | __synth__0 | type_tracker.rb:47:17:47:19 | __synth__0 | +| type_tracker.rb:48:5:48:13 | ...[...] | type_tracker.rb:48:5:48:13 | ...[...] | +| type_tracker.rb:48:12:48:12 | 1 | type_tracker.rb:48:12:48:12 | 1 | +| type_tracker.rb:50:5:50:10 | array4 | type_tracker.rb:50:5:50:10 | array4 | +| type_tracker.rb:50:14:50:26 | Array | type_tracker.rb:50:14:50:26 | Array | +| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:50:5:50:26 | ... = ... | +| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:50:5:50:26 | ... = ... | +| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:50:14:50:26 | call to [] | +| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:51:5:51:10 | array4 | +| type_tracker.rb:50:14:50:26 | call to [] | type_tracker.rb:52:5:52:10 | array4 | +| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:50:15:50:15 | 1 | +| type_tracker.rb:50:15:50:15 | 1 | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:50:17:50:17 | 2 | +| type_tracker.rb:50:17:50:17 | 2 | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:50:19:50:19 | 3 | +| type_tracker.rb:50:19:50:19 | 3 | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:50:21:50:21 | 4 | +| type_tracker.rb:50:21:50:21 | 4 | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:50:23:50:23 | 5 | +| type_tracker.rb:50:23:50:23 | 5 | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:50:25:50:25 | 6 | +| type_tracker.rb:50:25:50:25 | 6 | type_tracker.rb:52:5:52:13 | ...[...] | +| type_tracker.rb:51:5:51:10 | [post] array4 | type_tracker.rb:51:5:51:10 | [post] array4 | +| type_tracker.rb:51:5:51:10 | [post] array4 | type_tracker.rb:52:5:52:10 | array4 | +| type_tracker.rb:51:5:51:13 | call to []= | type_tracker.rb:51:5:51:13 | call to []= | +| type_tracker.rb:51:17:51:19 | __synth__0 | type_tracker.rb:51:17:51:19 | __synth__0 | +| type_tracker.rb:52:5:52:13 | ...[...] | type_tracker.rb:34:1:53:3 | return return in throughArray | +| type_tracker.rb:52:5:52:13 | ...[...] | type_tracker.rb:52:5:52:13 | ...[...] | forwardButNoBackwardFlow backwardButNoForwardFlow 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 9bdcda38316..2eb9121cfae 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 @@ -31,7 +31,7 @@ keyword(p1: 3, p2: 4) keyword(p2: 5, p1: 6) keyword(:p2 => 7, :p1 => 8) -def throughArray(obj, y) +def throughArray(obj, y, z) tmp = [obj] tmp[0] @@ -42,4 +42,12 @@ def throughArray(obj, y) array2 = [1,2,3,4,5,6] array2[0] = obj array2[y] + + array3 = [1,2,3,4,5,6] + array3[0] = obj + array3[1] + + array4 = [1,2,3,4,5,6] + array4[y] = obj + array4[z] end From 8704ccee77aecd828beb705a4dc15a5765b4ae61 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 28 Sep 2022 15:18:09 +0200 Subject: [PATCH 34/39] Ruby: mention TNoContentSet is only used by type-tracking --- ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index aff2c7ed72f..e04518cffce 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -385,7 +385,7 @@ private module Cached { FlowSummaryImplSpecific::ParsePositions::isParsedElementLowerBoundPosition(_, includeUnknown, lower) } or - TNoContentSet() + TNoContentSet() // Only used by type-tracking cached class TContentSet = From 76cab235d942f684e25ac8678266f68dceae1a94 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 28 Sep 2022 15:24:48 +0200 Subject: [PATCH 35/39] Ruby: reuse argumentPositionMatch --- .../ruby/typetracking/TypeTrackerSpecific.qll | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index ce8a48fcba5..dc608c26a1e 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -256,22 +256,9 @@ 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() + exists(DataFlowDispatch::ParameterPosition pos | + component = SummaryComponent::argument(pos) and + argumentPositionMatch(call.asExpr(), result, pos) ) or component = SummaryComponent::return() and From 3af3772041bd4f702da2471dbb64119eceb5c64a Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Wed, 28 Sep 2022 16:46:34 +0200 Subject: [PATCH 36/39] Ruby: Include `With(out)Element` in `isElementBody` --- .../dataflow/internal/FlowSummaryImplSpecific.qll | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll index 9926bfdf98d..78d2b1b4e1a 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll @@ -246,24 +246,15 @@ module ParsePositions { private import FlowSummaryImpl private predicate isParamBody(string body) { - exists(AccessPathToken tok | - tok.getName() = "Parameter" and - body = tok.getAnArgument() - ) + body = any(AccessPathToken tok).getAnArgument("Parameter") } private predicate isArgBody(string body) { - exists(AccessPathToken tok | - tok.getName() = "Argument" and - body = tok.getAnArgument() - ) + body = any(AccessPathToken tok).getAnArgument("Argument") } private predicate isElementBody(string body) { - exists(AccessPathToken tok | - tok.getName() = "Element" and - body = tok.getAnArgument() - ) + body = any(AccessPathToken tok).getAnArgument(["Element", "WithElement", "WithoutElement"]) } predicate isParsedParameterPosition(string c, int i) { From f1de5a2ffdff950f338e9293986e75066083872e Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 28 Sep 2022 16:37:14 +0200 Subject: [PATCH 37/39] Ruby: Restrict summaries and type trackers to relevant contents --- .../ql/lib/codeql/ruby/typetracking/TypeTracker.qll | 13 +++++++++---- .../ruby/typetracking/TypeTrackerSpecific.qll | 10 +++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index d689c900381..a4ac7550942 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -12,8 +12,8 @@ private module Cached { LevelStep() or CallStep() or ReturnStep() or - StoreStep(TypeTrackerContent content) or - LoadStep(TypeTrackerContent content) or + StoreStep(TypeTrackerContent content) { basicStoreStep(_, _, content) } or + LoadStep(TypeTrackerContent content) { basicLoadStep(_, _, content) } or JumpStep() pragma[nomagic] @@ -218,7 +218,10 @@ module StepSummary { } } -private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) +private newtype TTypeTracker = + MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) { + content = noContent() or basicStoreStep(_, _, content) + } /** * A summary of the steps needed to track a value to a given dataflow node. @@ -372,7 +375,9 @@ module TypeTracker { } private newtype TTypeBackTracker = - MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) + MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) { + content = noContent() or basicLoadStep(_, _, content) + } /** * A summary of the steps needed to back-track a use of a value to a given dataflow node. diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index dc608c26a1e..773751d1573 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -167,7 +167,7 @@ predicate returnStep(Node nodeFrom, Node nodeTo) { * to `z` inside `bar`, even though this content write happens _after_ `bar` is * called. */ -predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent contents) { +predicate basicStoreStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) { postUpdateStoreStep(nodeFrom, nodeTo, contents) or exists( @@ -185,7 +185,7 @@ predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent contents * 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) { +predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) { // TODO: support SetterMethodCall inside TuplePattern exists(ExprNodes::MethodCallCfgNode call | contents @@ -202,7 +202,7 @@ predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent con /** * Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`. */ -predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContent contents) { +predicate basicLoadStep(Node nodeFrom, Node nodeTo, DataFlow::ContentSet contents) { exists(ExprNodes::MethodCallCfgNode call | call.getExpr().getNumberOfArguments() = 0 and contents.isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr().getMethodName())) and @@ -231,7 +231,7 @@ class Boolean extends boolean { private import SummaryComponentStack private predicate hasStoreSummary( - SummarizedCallable callable, TypeTrackerContent contents, SummaryComponent input, + SummarizedCallable callable, DataFlow::ContentSet contents, SummaryComponent input, SummaryComponent output ) { callable @@ -240,7 +240,7 @@ private predicate hasStoreSummary( } private predicate hasLoadSummary( - SummarizedCallable callable, TypeTrackerContent contents, SummaryComponent input, + SummarizedCallable callable, DataFlow::ContentSet contents, SummaryComponent input, SummaryComponent output ) { callable From ae60b0ae6dc69e4b55e34f663f9cfe5da1ff7097 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 29 Sep 2022 15:54:51 +0200 Subject: [PATCH 38/39] Ruby: ensure pruning works with startInContent --- .../codeql/ruby/typetracking/TypeTracker.qll | 18 ++++++++++++++++-- .../dataflow/type-tracker/TypeTracker.expected | 8 -------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll index a4ac7550942..73ad4136aa6 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTracker.qll @@ -220,7 +220,15 @@ module StepSummary { private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) { - content = noContent() or basicStoreStep(_, _, content) + content = noContent() + or + // Restrict `content` to those that might eventually match a load. + // We can't rely on `basicStoreStep` since `startInContent` might be used with + // a content that has no corresponding store. + exists(TypeTrackerContent loadContents | + basicLoadStep(_, _, loadContents) and + compatibleContents(content, loadContents) + ) } /** @@ -376,7 +384,13 @@ module TypeTracker { private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) { - content = noContent() or basicLoadStep(_, _, content) + content = noContent() + or + // As in MkTypeTracker, restrict `content` to those that might eventually match a store. + exists(TypeTrackerContent storeContent | + basicStoreStep(_, _, storeContent) and + compatibleContents(storeContent, content) + ) } /** 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 3105ebda6b1..47504c44abd 100644 --- a/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected +++ b/ruby/ql/test/library-tests/dataflow/type-tracker/TypeTracker.expected @@ -156,10 +156,6 @@ track | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:40:5:40:12 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:44:5:44:13 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content 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 attribute [] | type_tracker.rb:47:5:47:10 | [post] array3 | -| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content attribute [] | type_tracker.rb:51:5:51:10 | [post] array4 | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:38:13:38:25 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element | type_tracker.rb:50:14:50:26 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] | @@ -173,10 +169,6 @@ track | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:40:5:40:12 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:44:5:44:13 | ...[...] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:52:5:52:13 | ...[...] | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content 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 attribute [] | type_tracker.rb:47:5:47:10 | [post] array3 | -| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content attribute [] | type_tracker.rb:51:5:51:10 | [post] array4 | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:38:13:38:25 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element | type_tracker.rb:50:14:50:26 | call to [] | | type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 or unknown | type_tracker.rb:35:11:35:15 | call to [] | From ed36f1983bf469749fd35a2f28575c6a11e87c53 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 29 Sep 2022 15:57:09 +0200 Subject: [PATCH 39/39] Python: sync TypeTracker.qll --- .../dataflow/new/internal/TypeTracker.qll | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) 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 d689c900381..73ad4136aa6 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/TypeTracker.qll @@ -12,8 +12,8 @@ private module Cached { LevelStep() or CallStep() or ReturnStep() or - StoreStep(TypeTrackerContent content) or - LoadStep(TypeTrackerContent content) or + StoreStep(TypeTrackerContent content) { basicStoreStep(_, _, content) } or + LoadStep(TypeTrackerContent content) { basicLoadStep(_, _, content) } or JumpStep() pragma[nomagic] @@ -218,7 +218,18 @@ module StepSummary { } } -private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) +private newtype TTypeTracker = + MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) { + content = noContent() + or + // Restrict `content` to those that might eventually match a load. + // We can't rely on `basicStoreStep` since `startInContent` might be used with + // a content that has no corresponding store. + exists(TypeTrackerContent loadContents | + basicLoadStep(_, _, loadContents) and + compatibleContents(content, loadContents) + ) + } /** * A summary of the steps needed to track a value to a given dataflow node. @@ -372,7 +383,15 @@ module TypeTracker { } private newtype TTypeBackTracker = - MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) + MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) { + content = noContent() + or + // As in MkTypeTracker, restrict `content` to those that might eventually match a store. + exists(TypeTrackerContent storeContent | + basicStoreStep(_, _, storeContent) and + compatibleContents(storeContent, content) + ) + } /** * A summary of the steps needed to back-track a use of a value to a given dataflow node.