diff --git a/config/identical-files.json b/config/identical-files.json index 144031d5a686d..2d6a5ff025465 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -57,7 +57,6 @@ "java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll", "csharp/ql/lib/semmle/code/csharp/dataflow/internal/FlowSummaryImpl.qll", "go/ql/lib/semmle/go/dataflow/internal/FlowSummaryImpl.qll", - "ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll", "python/ql/lib/semmle/python/dataflow/new/internal/FlowSummaryImpl.qll", "swift/ql/lib/codeql/swift/dataflow/internal/FlowSummaryImpl.qll" ], @@ -534,4 +533,4 @@ "python/ql/test/experimental/dataflow/model-summaries/InlineTaintTest.ext.yml", "python/ql/test/experimental/dataflow/model-summaries/NormalDataflowTest.ext.yml" ] -} +} \ No newline at end of file diff --git a/ruby/ql/docs/flow_summaries.md b/ruby/ql/docs/flow_summaries.md index 0bc8c5e190a43..5f39e1585884a 100644 --- a/ruby/ql/docs/flow_summaries.md +++ b/ruby/ql/docs/flow_summaries.md @@ -22,7 +22,7 @@ have no source code, so we include a flow summary for it: private class ChompSummary extends SimpleSummarizedCallable { ChompSummary() { this = "chomp" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and output = "ReturnValue" and preservesValue = false diff --git a/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll index 7e7004c6e619c..d7ba0898335aa 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll @@ -8,7 +8,6 @@ private import internal.FlowSummaryImpl as Impl private import internal.DataFlowDispatch private import internal.DataFlowImplCommon as DataFlowImplCommon private import internal.DataFlowPrivate -private import internal.FlowSummaryImplSpecific // import all instances below private module Summaries { @@ -16,37 +15,45 @@ private module Summaries { private import codeql.ruby.frameworks.data.ModelsAsData } -class SummaryComponent = Impl::Public::SummaryComponent; +deprecated class SummaryComponent = Impl::Private::SummaryComponent; -/** Provides predicates for constructing summary components. */ -module SummaryComponent { - private import Impl::Public::SummaryComponent as SC +/** + * DEPRECATED. + * + * Provides predicates for constructing summary components. + */ +deprecated module SummaryComponent { + private import Impl::Private::SummaryComponent as SC - predicate parameter = SC::parameter/1; + deprecated predicate parameter = SC::parameter/1; - predicate argument = SC::argument/1; + deprecated predicate argument = SC::argument/1; - predicate content = SC::content/1; + deprecated predicate content = SC::content/1; - predicate withoutContent = SC::withoutContent/1; + deprecated predicate withoutContent = SC::withoutContent/1; - predicate withContent = SC::withContent/1; + deprecated predicate withContent = SC::withContent/1; - class SyntheticGlobal = SC::SyntheticGlobal; + deprecated class SyntheticGlobal = Impl::Private::SyntheticGlobal; /** Gets a summary component that represents a receiver. */ - SummaryComponent receiver() { result = argument(any(ParameterPosition pos | pos.isSelf())) } + deprecated SummaryComponent receiver() { + result = argument(any(ParameterPosition pos | pos.isSelf())) + } /** Gets a summary component that represents a block argument. */ - SummaryComponent block() { result = argument(any(ParameterPosition pos | pos.isBlock())) } + deprecated SummaryComponent block() { + result = argument(any(ParameterPosition pos | pos.isBlock())) + } /** Gets a summary component that represents an element in a collection at an unknown index. */ - SummaryComponent elementUnknown() { + deprecated SummaryComponent elementUnknown() { result = SC::content(TSingletonContent(TUnknownElementContent())) } /** Gets a summary component that represents an element in a collection at a known index. */ - SummaryComponent elementKnown(ConstantValue cv) { + deprecated SummaryComponent elementKnown(ConstantValue cv) { result = SC::content(TSingletonContent(DataFlow::Content::getElementContent(cv))) } @@ -54,7 +61,7 @@ module SummaryComponent { * Gets a summary component that represents an element in a collection at a specific * known index `cv`, or an unknown index. */ - SummaryComponent elementKnownOrUnknown(ConstantValue cv) { + deprecated SummaryComponent elementKnownOrUnknown(ConstantValue cv) { result = SC::content(TKnownOrUnknownElementContent(TKnownElementContent(cv))) or not exists(TKnownElementContent(cv)) and @@ -71,13 +78,13 @@ module SummaryComponent { * * but is more efficient, because it is represented by a single value. */ - SummaryComponent elementAny() { result = SC::content(TAnyElementContent()) } + deprecated SummaryComponent elementAny() { result = SC::content(TAnyElementContent()) } /** * Gets a summary component that represents an element in a collection at known * integer index `lower` or above. */ - SummaryComponent elementLowerBound(int lower) { + deprecated SummaryComponent elementLowerBound(int lower) { result = SC::content(TElementLowerBoundContent(lower, false)) } @@ -85,34 +92,38 @@ module SummaryComponent { * Gets a summary component that represents an element in a collection at known * integer index `lower` or above, or possibly at an unknown index. */ - SummaryComponent elementLowerBoundOrUnknown(int lower) { + deprecated SummaryComponent elementLowerBoundOrUnknown(int lower) { result = SC::content(TElementLowerBoundContent(lower, true)) } /** Gets a summary component that represents the return value of a call. */ - SummaryComponent return() { result = SC::return(any(NormalReturnKind rk)) } + deprecated SummaryComponent return() { result = SC::return(any(NormalReturnKind rk)) } } -class SummaryComponentStack = Impl::Public::SummaryComponentStack; +deprecated class SummaryComponentStack = Impl::Private::SummaryComponentStack; -/** Provides predicates for constructing stacks of summary components. */ -module SummaryComponentStack { - private import Impl::Public::SummaryComponentStack as SCS +/** + * DEPRECATED. + * + * Provides predicates for constructing stacks of summary components. + */ +deprecated module SummaryComponentStack { + private import Impl::Private::SummaryComponentStack as SCS - predicate singleton = SCS::singleton/1; + deprecated predicate singleton = SCS::singleton/1; - predicate push = SCS::push/2; + deprecated predicate push = SCS::push/2; - predicate argument = SCS::argument/1; + deprecated predicate argument = SCS::argument/1; /** Gets a singleton stack representing a receiver. */ - SummaryComponentStack receiver() { result = singleton(SummaryComponent::receiver()) } + deprecated SummaryComponentStack receiver() { result = singleton(SummaryComponent::receiver()) } /** Gets a singleton stack representing a block argument. */ - SummaryComponentStack block() { result = singleton(SummaryComponent::block()) } + deprecated SummaryComponentStack block() { result = singleton(SummaryComponent::block()) } /** Gets a singleton stack representing the return value of a call. */ - SummaryComponentStack return() { result = singleton(SummaryComponent::return()) } + deprecated SummaryComponentStack return() { result = singleton(SummaryComponent::return()) } } /** A callable with a flow summary, identified by a unique string. */ @@ -121,18 +132,12 @@ abstract class SummarizedCallable extends LibraryCallable, Impl::Public::Summari SummarizedCallable() { any() } /** - * Same as - * - * ```ql - * propagatesFlow( - * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue - * ) - * ``` - * - * but uses an external (string) representation of the input and output stacks. + * DEPRECATED: Use `propagatesFlow` instead. */ pragma[nomagic] - predicate propagatesFlowExt(string input, string output, boolean preservesValue) { none() } + deprecated predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + none() + } /** * Gets the synthesized parameter that results from an input specification @@ -141,7 +146,7 @@ abstract class SummarizedCallable extends LibraryCallable, Impl::Public::Summari DataFlow::ParameterNode getParameter(string s) { exists(ParameterPosition pos | DataFlowImplCommon::parameterNode(result, TLibraryCallable(this), pos) and - s = getParameterPosition(pos) + s = Impl::Input::encodeParameterPosition(pos) ) } } @@ -159,7 +164,7 @@ abstract class SimpleSummarizedCallable extends SummarizedCallable { final override MethodCall getACallSimple() { result = mc } } -class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack; +deprecated class RequiredSummaryComponentStack = Impl::Private::RequiredSummaryComponentStack; /** * Provides a set of special flow summaries to ensure that callbacks passed into @@ -199,7 +204,7 @@ private module LibraryCallbackSummaries { libraryCallHasLambdaArg(result.getAControlFlowNode(), _) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[block]" and output = "Argument[block].Parameter[lambda-self]" diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/AccessPathSyntax.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/AccessPathSyntax.qll deleted file mode 100644 index 0c3dc8427b2b1..0000000000000 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/AccessPathSyntax.qll +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Module for parsing access paths from MaD models, both the identifying access path used - * by dynamic languages, and the input/output specifications for summary steps. - * - * This file is used by the shared data flow library and by the JavaScript libraries - * (which does not use the shared data flow libraries). - */ - -/** - * Convenience-predicate for extracting two capture groups at once. - */ -bindingset[input, regexp] -private predicate regexpCaptureTwo(string input, string regexp, string capture1, string capture2) { - capture1 = input.regexpCapture(regexp, 1) and - capture2 = input.regexpCapture(regexp, 2) -} - -/** Companion module to the `AccessPath` class. */ -module AccessPath { - /** A string that should be parsed as an access path. */ - abstract class Range extends string { - bindingset[this] - Range() { any() } - } - - /** - * Parses an integer constant `n` or interval `n1..n2` (inclusive) and gets the value - * of the constant or any value contained in the interval. - */ - bindingset[arg] - int parseInt(string arg) { - result = arg.toInt() - or - // Match "n1..n2" - exists(string lo, string hi | - regexpCaptureTwo(arg, "(-?\\d+)\\.\\.(-?\\d+)", lo, hi) and - result = [lo.toInt() .. hi.toInt()] - ) - } - - /** - * Parses a lower-bounded interval `n..` and gets the lower bound. - */ - bindingset[arg] - int parseLowerBound(string arg) { result = arg.regexpCapture("(-?\\d+)\\.\\.", 1).toInt() } - - /** - * Parses an integer constant or interval (bounded or unbounded) that explicitly - * references the arity, such as `N-1` or `N-3..N-1`. - * - * Note that expressions of form `N-x` will never resolve to a negative index, - * even if `N` is zero (it will have no result in that case). - */ - bindingset[arg, arity] - private int parseIntWithExplicitArity(string arg, int arity) { - result >= 0 and // do not allow N-1 to resolve to a negative index - exists(string lo | - // N-x - lo = arg.regexpCapture("N-(\\d+)", 1) and - result = arity - lo.toInt() - or - // N-x.. - lo = arg.regexpCapture("N-(\\d+)\\.\\.", 1) and - result = [arity - lo.toInt(), arity - 1] - ) - or - exists(string lo, string hi | - // x..N-y - regexpCaptureTwo(arg, "(-?\\d+)\\.\\.N-(\\d+)", lo, hi) and - result = [lo.toInt() .. arity - hi.toInt()] - or - // N-x..N-y - regexpCaptureTwo(arg, "N-(\\d+)\\.\\.N-(\\d+)", lo, hi) and - result = [arity - lo.toInt() .. arity - hi.toInt()] and - result >= 0 - or - // N-x..y - regexpCaptureTwo(arg, "N-(\\d+)\\.\\.(\\d+)", lo, hi) and - result = [arity - lo.toInt() .. hi.toInt()] and - result >= 0 - ) - } - - /** - * Parses an integer constant or interval (bounded or unbounded) and gets any - * of the integers contained within (of which there may be infinitely many). - * - * Has no result for arguments involving an explicit arity, such as `N-1`. - */ - bindingset[arg, result] - int parseIntUnbounded(string arg) { - result = parseInt(arg) - or - result >= parseLowerBound(arg) - } - - /** - * Parses an integer constant or interval (bounded or unbounded) that - * may reference the arity of a call, such as `N-1` or `N-3..N-1`. - * - * Note that expressions of form `N-x` will never resolve to a negative index, - * even if `N` is zero (it will have no result in that case). - */ - bindingset[arg, arity] - int parseIntWithArity(string arg, int arity) { - result = parseInt(arg) - or - result in [parseLowerBound(arg) .. arity - 1] - or - result = parseIntWithExplicitArity(arg, arity) - } -} - -/** Gets the `n`th token on the access path as a string. */ -private string getRawToken(AccessPath path, int n) { - // Avoid splitting by '.' since tokens may contain dots, e.g. `Field[foo.Bar.x]`. - // Instead use regexpFind to match valid tokens, and supplement with a final length - // check (in `AccessPath.hasSyntaxError`) to ensure all characters were included in a token. - result = path.regexpFind("\\w+(?:\\[[^\\]]*\\])?(?=\\.|$)", n, _) -} - -/** - * A string that occurs as an access path (either identifying or input/output spec) - * which might be relevant for this database. - */ -class AccessPath extends string instanceof AccessPath::Range { - /** Holds if this string is not a syntactically valid access path. */ - predicate hasSyntaxError() { - // If the lengths match, all characters must haven been included in a token - // or seen by the `.` lookahead pattern. - this != "" and - not this.length() = sum(int n | | getRawToken(this, n).length() + 1) - 1 - } - - /** Gets the `n`th token on the access path (if there are no syntax errors). */ - AccessPathToken getToken(int n) { - result = getRawToken(this, n) and - not this.hasSyntaxError() - } - - /** Gets the number of tokens on the path (if there are no syntax errors). */ - int getNumToken() { - result = count(int n | exists(getRawToken(this, n))) and - not this.hasSyntaxError() - } -} - -/** - * An access part token such as `Argument[1]` or `ReturnValue`, appearing in one or more access paths. - */ -class AccessPathToken extends string { - AccessPathToken() { this = getRawToken(_, _) } - - private string getPart(int part) { - result = this.regexpCapture("([^\\[]+)(?:\\[([^\\]]*)\\])?", part) - } - - /** Gets the name of the token, such as `Member` from `Member[x]` */ - string getName() { result = this.getPart(1) } - - /** - * Gets the argument list, such as `1,2` from `Member[1,2]`, - * or has no result if there are no arguments. - */ - string getArgumentList() { result = this.getPart(2) } - - /** Gets the `n`th argument to this token, such as `x` or `y` from `Member[x,y]`. */ - string getArgument(int n) { result = this.getArgumentList().splitAt(",", n).trim() } - - /** Gets the `n`th argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */ - pragma[nomagic] - string getArgument(string name, int n) { name = this.getName() and result = this.getArgument(n) } - - /** Gets an argument to this token, such as `x` or `y` from `Member[x,y]`. */ - string getAnArgument() { result = this.getArgument(_) } - - /** Gets an argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */ - string getAnArgument(string name) { result = this.getArgument(name, _) } - - /** Gets the number of arguments to this token, such as 2 for `Member[x,y]` or zero for `ReturnValue`. */ - int getNumArgument() { result = count(int n | exists(this.getArgument(n))) } -} diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll index 0668b6a357fb8..f7e6d381d8f85 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll @@ -5,7 +5,6 @@ private import codeql.ruby.typetracking.TypeTracker private import codeql.ruby.typetracking.TypeTrackerSpecific as TypeTrackerSpecific private import codeql.ruby.ast.internal.Module private import FlowSummaryImpl as FlowSummaryImpl -private import FlowSummaryImplSpecific as FlowSummaryImplSpecific private import codeql.ruby.dataflow.FlowSummary private import codeql.ruby.dataflow.SSA private import codeql.util.Boolean @@ -427,14 +426,14 @@ private module Cached { TPositionalArgumentPosition(int pos) { exists(Call c | exists(c.getArgument(pos))) or - FlowSummaryImplSpecific::ParsePositions::isParsedParameterPosition(_, pos) + FlowSummaryImpl::ParsePositions::isParsedParameterPosition(_, pos) } or TKeywordArgumentPosition(string name) { name = any(KeywordParameter kp).getName() or exists(any(Call c).getKeywordArgument(name)) or - FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name) + FlowSummaryImpl::ParsePositions::isParsedKeywordParameterPosition(_, name) } or THashSplatArgumentPosition() or TSynthHashSplatArgumentPosition() or @@ -451,15 +450,17 @@ private module Cached { TPositionalParameterPosition(int pos) { pos = any(Parameter p).getPosition() or - FlowSummaryImplSpecific::ParsePositions::isParsedArgumentPosition(_, pos) + FlowSummaryImpl::ParsePositions::isParsedArgumentPosition(_, pos) } or TPositionalParameterLowerBoundPosition(int pos) { - FlowSummaryImplSpecific::ParsePositions::isParsedArgumentLowerBoundPosition(_, pos) + FlowSummaryImpl::ParsePositions::isParsedArgumentLowerBoundPosition(_, pos) } or TKeywordParameterPosition(string name) { name = any(KeywordParameter kp).getName() or - FlowSummaryImplSpecific::ParsePositions::isParsedKeywordArgumentPosition(_, name) + exists(any(Call c).getKeywordArgument(name)) + or + FlowSummaryImpl::ParsePositions::isParsedKeywordArgumentPosition(_, name) } or THashSplatParameterPosition() or TSynthHashSplatParameterPosition() or diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index d9a0ad2c6c9c6..d440e28689a8a 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -8,7 +8,6 @@ private import DataFlowPublic private import DataFlowDispatch private import SsaImpl as SsaImpl private import FlowSummaryImpl as FlowSummaryImpl -private import FlowSummaryImplSpecific as FlowSummaryImplSpecific private import codeql.ruby.frameworks.data.ModelsAsData /** Gets the callable in which this node occurs. */ @@ -609,8 +608,7 @@ private module Cached { TAnyElementContent() or TKnownOrUnknownElementContent(Content::KnownElementContent c) or TElementLowerBoundContent(int lower, boolean includeUnknown) { - FlowSummaryImplSpecific::ParsePositions::isParsedElementLowerBoundPosition(_, includeUnknown, - lower) + FlowSummaryImpl::ParsePositions::isParsedElementLowerBoundPosition(_, includeUnknown, lower) } or TElementContentOfTypeContent(string type, Boolean includeUnknown) { type = any(Content::KnownElementContent content).getIndex().getValueType() @@ -680,6 +678,21 @@ private module Cached { THashSplatContentApprox(string approx) { approx = approxKnownElementIndex(_) } or TNonElementContentApprox(Content c) { not c instanceof Content::ElementContent } or TCapturedVariableContentApprox(VariableCapture::CapturedVariable v) + + cached + newtype TDataFlowType = + TLambdaDataFlowType(Callable c) { c = any(LambdaSelfReferenceNode n).getCallable() } or + // In order to reduce the set of cons-candidates, we annotate all implicit (hash) splat + // creations with the name of the method that they are passed into. This includes + // array/hash literals as well (where the name is simply `[]`), because of how they + // are modeled (see `Array.qll` and `Hash.qll`). + TSynthHashSplatArgumentType(string methodName) { + methodName = any(SynthHashSplatArgumentNode n).getMethodName() + } or + TSynthSplatArgumentType(string methodName) { + methodName = any(SynthSplatArgumentNode n).getMethodName() + } or + TUnknownDataFlowType() } class TElementContent = @@ -1234,11 +1247,11 @@ module ArgumentNodes { } private class SummaryArgumentNode extends FlowSummaryNode, ArgumentNode { - private DataFlowCall call_; + private SummaryCall call_; private ArgumentPosition pos_; SummaryArgumentNode() { - FlowSummaryImpl::Private::summaryArgumentNode(call_, this.getSummaryNode(), pos_) + FlowSummaryImpl::Private::summaryArgumentNode(call_.getReceiver(), this.getSummaryNode(), pos_) } override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) { @@ -1604,11 +1617,11 @@ private module OutNodes { } private class SummaryOutNode extends FlowSummaryNode, OutNode { - private DataFlowCall call; + private SummaryCall call; private ReturnKind kind_; SummaryOutNode() { - FlowSummaryImpl::Private::summaryOutNode(call, this.getSummaryNode(), kind_) + FlowSummaryImpl::Private::summaryOutNode(call.getReceiver(), this.getSummaryNode(), kind_) } override DataFlowCall getCall(ReturnKind kind) { result = call and kind = kind_ } @@ -1766,20 +1779,6 @@ predicate expectsContent(Node n, ContentSet c) { FlowSummaryImpl::Private::Steps::summaryExpectsContent(n.(FlowSummaryNode).getSummaryNode(), c) } -private newtype TDataFlowType = - TLambdaDataFlowType(Callable c) { c = any(LambdaSelfReferenceNode n).getCallable() } or - // In order to reduce the set of cons-candidates, we annotate all implicit (hash) splat - // creations with the name of the method that they are passed into. This includes - // array/hash literals as well (where the name is simply `[]`), because of how they - // are modeled (see `Array.qll` and `Hash.qll`). - TSynthHashSplatArgumentType(string methodName) { - methodName = any(SynthHashSplatArgumentNode n).getMethodName() - } or - TSynthSplatArgumentType(string methodName) { - methodName = any(SynthSplatArgumentNode n).getMethodName() - } or - TUnknownDataFlowType() - class DataFlowType extends TDataFlowType { string toString() { result = "" } } @@ -2006,7 +2005,10 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves * by default as a heuristic. */ predicate allowParameterReturnInSelf(ParameterNodeImpl p) { - FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(p) + exists(DataFlowCallable c, ParameterPosition pos | + p.isParameterOf(c, pos) and + FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(c.asLibraryCallable(), pos) + ) or VariableCapture::Flow::heuristicAllowInstanceParameterReturnInSelf(p.(SelfParameterNode) .getCallable()) diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll index 0aa17c521b43f..413dd5d361591 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll @@ -1,1491 +1,221 @@ /** * Provides classes and predicates for defining flow summaries. - * - * The definitions in this file are language-independent, and language-specific - * definitions are passed in via the `DataFlowImplSpecific` and - * `FlowSummaryImplSpecific` modules. */ -private import FlowSummaryImplSpecific +private import codeql.dataflow.internal.FlowSummaryImpl +private import codeql.dataflow.internal.AccessPathSyntax as AccessPath +private import codeql.ruby.AST +private import codeql.ruby.dataflow.internal.DataFlowImplSpecific as DataFlowImplSpecific private import DataFlowImplSpecific::Private private import DataFlowImplSpecific::Public -private import DataFlowImplCommon -private import codeql.util.Unit -/** Provides classes and predicates for defining flow summaries. */ -module Public { - private import Private - - /** - * A component used in a flow summary. - * - * Either a parameter or an argument at a given position, a specific - * content type, or a return kind. - */ - class SummaryComponent extends TSummaryComponent { - /** Gets a textual representation of this component used for MaD models. */ - string getMadRepresentation() { - result = getMadRepresentationSpecific(this) - or - exists(ArgumentPosition pos | - this = TParameterSummaryComponent(pos) and - result = "Parameter[" + getArgumentPosition(pos) + "]" - ) - or - exists(ParameterPosition pos | - this = TArgumentSummaryComponent(pos) and - result = "Argument[" + getParameterPosition(pos) + "]" - ) - or - exists(string synthetic | - this = TSyntheticGlobalSummaryComponent(synthetic) and - result = "SyntheticGlobal[" + synthetic + "]" - ) - or - this = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue" - } - - /** Gets a textual representation of this summary component. */ - string toString() { result = this.getMadRepresentation() } - } - - /** Provides predicates for constructing summary components. */ - module SummaryComponent { - /** Gets a summary component for content `c`. */ - SummaryComponent content(ContentSet c) { result = TContentSummaryComponent(c) } - - /** Gets a summary component where data is not allowed to be stored in `c`. */ - SummaryComponent withoutContent(ContentSet c) { result = TWithoutContentSummaryComponent(c) } - - /** Gets a summary component where data must be stored in `c`. */ - SummaryComponent withContent(ContentSet c) { result = TWithContentSummaryComponent(c) } - - /** Gets a summary component for a parameter at position `pos`. */ - SummaryComponent parameter(ArgumentPosition pos) { result = TParameterSummaryComponent(pos) } - - /** Gets a summary component for an argument at position `pos`. */ - SummaryComponent argument(ParameterPosition pos) { result = TArgumentSummaryComponent(pos) } - - /** Gets a summary component for a return of kind `rk`. */ - SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) } - - /** Gets a summary component for synthetic global `sg`. */ - SummaryComponent syntheticGlobal(SyntheticGlobal sg) { - result = TSyntheticGlobalSummaryComponent(sg) - } - - /** - * A synthetic global. This represents some form of global state, which - * summaries can read and write individually. - */ - abstract class SyntheticGlobal extends string { - bindingset[this] - SyntheticGlobal() { any() } - } - } - - /** - * A (non-empty) stack of summary components. - * - * A stack is used to represent where data is read from (input) or where it - * is written to (output). For example, an input stack `[Field f, Argument 0]` - * means that data is read from field `f` from the `0`th argument, while an - * output stack `[Field g, Return]` means that data is written to the field - * `g` of the returned object. - */ - class SummaryComponentStack extends TSummaryComponentStack { - /** Gets the head of this stack. */ - SummaryComponent head() { - this = TSingletonSummaryComponentStack(result) or - this = TConsSummaryComponentStack(result, _) - } - - /** Gets the tail of this stack, if any. */ - SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) } - - /** Gets the length of this stack. */ - int length() { - this = TSingletonSummaryComponentStack(_) and result = 1 - or - result = 1 + this.tail().length() - } - - /** Gets the stack obtained by dropping the first `i` elements, if any. */ - SummaryComponentStack drop(int i) { - i = 0 and result = this - or - result = this.tail().drop(i - 1) - } - - /** Holds if this stack contains summary component `c`. */ - predicate contains(SummaryComponent c) { c = this.drop(_).head() } - - /** Gets the bottom element of this stack. */ - SummaryComponent bottom() { - this = TSingletonSummaryComponentStack(result) or result = this.tail().bottom() - } - - /** Gets a textual representation of this stack used for MaD models. */ - string getMadRepresentation() { - exists(SummaryComponent head, SummaryComponentStack tail | - head = this.head() and - tail = this.tail() and - result = tail.getMadRepresentation() + "." + head.getMadRepresentation() - ) - or - exists(SummaryComponent c | - this = TSingletonSummaryComponentStack(c) and - result = c.getMadRepresentation() - ) - } +module Input implements InputSig { + class SummarizedCallableBase = string; - /** Gets a textual representation of this stack. */ - string toString() { result = this.getMadRepresentation() } - } - - /** Provides predicates for constructing stacks of summary components. */ - module SummaryComponentStack { - /** Gets a singleton stack containing `c`. */ - SummaryComponentStack singleton(SummaryComponent c) { - result = TSingletonSummaryComponentStack(c) - } - - /** - * Gets the stack obtained by pushing `head` onto `tail`. - * - * Make sure to override `RequiredSummaryComponentStack::required()` in order - * to ensure that the constructed stack exists. - */ - SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) { - result = TConsSummaryComponentStack(head, tail) - } - - /** Gets a singleton stack for an argument at position `pos`. */ - SummaryComponentStack argument(ParameterPosition pos) { - result = singleton(SummaryComponent::argument(pos)) - } - - /** Gets a singleton stack representing a return of kind `rk`. */ - SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) } - } - - /** - * A class that exists for QL technical reasons only (the IPA type used - * to represent component stacks needs to be bounded). - */ - class RequiredSummaryComponentStack extends Unit { - /** - * Holds if the stack obtained by pushing `head` onto `tail` is required. - */ - abstract predicate required(SummaryComponent head, SummaryComponentStack tail); - } - - /** - * Gets the valid model origin values. - */ - private string getValidModelOrigin() { - result = - [ - "ai", // AI (machine learning) - "df", // Dataflow (model generator) - "tb", // Type based (model generator) - "hq", // Heuristic query - ] - } - - /** - * A class used to represent provenance values for MaD models. - * - * The provenance value is a string of the form `origin-verification` - * (or just `manual`), where `origin` is a value indicating the - * origin of the model, and `verification` is a value indicating, how - * the model was verified. - * - * Examples could be: - * - `df-generated`: A model produced by the model generator, but not verified by a human. - * - `ai-manual`: A model produced by AI, but verified by a human. - */ - class Provenance extends string { - private string verification; - - Provenance() { - exists(string origin | origin = getValidModelOrigin() | - this = origin + "-" + verification and - verification = ["manual", "generated"] - ) - or - this = verification and verification = "manual" - } - - /** - * Holds if this is a valid generated provenance value. - */ - predicate isGenerated() { verification = "generated" } - - /** - * Holds if this is a valid manual provenance value. - */ - predicate isManual() { verification = "manual" } - } - - /** A callable with a flow summary. */ - abstract class SummarizedCallable extends SummarizedCallableBase { - bindingset[this] - SummarizedCallable() { any() } - - /** - * Holds if data may flow from `input` to `output` through this callable. - * - * `preservesValue` indicates whether this is a value-preserving step - * or a taint-step. - * - * Input specifications are restricted to stacks that end with - * `SummaryComponent::argument(_)`, preceded by zero or more - * `SummaryComponent::return(_)` or `SummaryComponent::content(_)` components. - * - * Output specifications are restricted to stacks that end with - * `SummaryComponent::return(_)` or `SummaryComponent::argument(_)`. - * - * Output stacks ending with `SummaryComponent::return(_)` can be preceded by zero - * or more `SummaryComponent::content(_)` components. - * - * Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an - * optional `SummaryComponent::parameter(_)` component, which in turn can be preceded - * by zero or more `SummaryComponent::content(_)` components. - */ - pragma[nomagic] - predicate propagatesFlow( - SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue - ) { - none() - } - - /** - * Holds if there exists a generated summary that applies to this callable. - */ - final predicate hasGeneratedModel() { - exists(Provenance p | p.isGenerated() and this.hasProvenance(p)) - } + ArgumentPosition callbackSelfParameterPosition() { result.isLambdaSelf() } - /** - * Holds if all the summaries that apply to this callable are auto generated and not manually created. - * That is, only apply generated models, when there are no manual models. - */ - final predicate applyGeneratedModel() { - this.hasGeneratedModel() and - not this.hasManualModel() - } + DataFlowType getContentType(ContentSet c) { result = TUnknownDataFlowType() and exists(c) } - /** - * Holds if there exists a manual summary that applies to this callable. - */ - final predicate hasManualModel() { - exists(Provenance p | p.isManual() and this.hasProvenance(p)) - } - - /** - * Holds if there exists a manual summary that applies to this callable. - * Always apply manual models if they exist. - */ - final predicate applyManualModel() { this.hasManualModel() } - - /** - * Holds if there exists a summary that applies to this callable - * that has provenance `provenance`. - */ - predicate hasProvenance(Provenance provenance) { provenance = "manual" } + bindingset[c, pos] + DataFlowType getParameterType(SummarizedCallableBase c, ParameterPosition pos) { + result = TUnknownDataFlowType() and exists(c) and exists(pos) } - /** - * A callable where there is no flow via the callable. - */ - class NeutralSummaryCallable extends NeutralCallable { - NeutralSummaryCallable() { this.getKind() = "summary" } + bindingset[c, rk] + DataFlowType getReturnType(SummarizedCallableBase c, ReturnKind rk) { + result = TUnknownDataFlowType() and exists(c) and exists(rk) } - /** - * A callable that has a neutral model. - */ - class NeutralCallable extends NeutralCallableBase { - private string kind; - private Provenance provenance; - - NeutralCallable() { neutralElement(this, kind, provenance) } - - /** - * Holds if the neutral is auto generated. - */ - final predicate hasGeneratedModel() { provenance.isGenerated() } - - /** - * Holds if there exists a manual neutral that applies to this callable. - */ - final predicate hasManualModel() { provenance.isManual() } - - /** - * Holds if the neutral has provenance `p`. - */ - predicate hasProvenance(Provenance p) { p = provenance } - - /** - * Gets the kind of the neutral. - */ - string getKind() { result = kind } + bindingset[t, pos] + DataFlowType getCallbackParameterType(DataFlowType t, ArgumentPosition pos) { + result = TUnknownDataFlowType() and exists(t) and exists(pos) } -} - -/** - * Provides predicates for compiling flow summaries down to atomic local steps, - * read steps, and store steps. - */ -module Private { - private import Public - import AccessPathSyntax - newtype TSummaryComponent = - TContentSummaryComponent(ContentSet c) or - TParameterSummaryComponent(ArgumentPosition pos) or - TArgumentSummaryComponent(ParameterPosition pos) or - TReturnSummaryComponent(ReturnKind rk) or - TSyntheticGlobalSummaryComponent(SummaryComponent::SyntheticGlobal sg) or - TWithoutContentSummaryComponent(ContentSet c) or - TWithContentSummaryComponent(ContentSet c) - - private TParameterSummaryComponent callbackSelfParam() { - result = TParameterSummaryComponent(callbackSelfParameterPosition()) + bindingset[t, rk] + DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { + result = TUnknownDataFlowType() and exists(t) and exists(rk) } - newtype TSummaryComponentStack = - TSingletonSummaryComponentStack(SummaryComponent c) or - TConsSummaryComponentStack(SummaryComponent head, SummaryComponentStack tail) { - any(RequiredSummaryComponentStack x).required(head, tail) - or - any(RequiredSummaryComponentStack x).required(TParameterSummaryComponent(_), tail) and - head = callbackSelfParam() - or - derivedFluentFlowPush(_, _, _, head, tail, _) - } + ReturnKind getStandardReturnValueKind() { result instanceof NormalReturnKind } - pragma[nomagic] - private predicate summary( - SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output, - boolean preservesValue - ) { - c.propagatesFlow(input, output, preservesValue) - or - // observe side effects of callbacks on input arguments - c.propagatesFlow(output, input, preservesValue) and - preservesValue = true and - isCallbackParameter(input) and - isContentOfArgument(output, _) - or - // flow from the receiver of a callback into the instance-parameter - exists(SummaryComponentStack s, SummaryComponentStack callbackRef | - c.propagatesFlow(s, _, _) or c.propagatesFlow(_, s, _) - | - callbackRef = s.drop(_) and - (isCallbackParameter(callbackRef) or callbackRef.head() = TReturnSummaryComponent(_)) and - input = callbackRef.tail() and - output = TConsSummaryComponentStack(callbackSelfParam(), input) and - preservesValue = true + string encodeParameterPosition(ParameterPosition pos) { + exists(int i | + pos.isPositional(i) and + result = i.toString() ) or - exists(SummaryComponentStack arg, SummaryComponentStack return | - derivedFluentFlow(c, input, arg, return, preservesValue) - | - arg.length() = 1 and - output = return - or - exists(SummaryComponent head, SummaryComponentStack tail | - derivedFluentFlowPush(c, input, arg, head, tail, 0) and - output = SummaryComponentStack::push(head, tail) - ) + exists(int i | + pos.isPositionalLowerBound(i) and + result = i + ".." ) or - // Chain together summaries where values get passed into callbacks along the way - exists(SummaryComponentStack mid, boolean preservesValue1, boolean preservesValue2 | - c.propagatesFlow(input, mid, preservesValue1) and - c.propagatesFlow(mid, output, preservesValue2) and - mid.drop(mid.length() - 2) = - SummaryComponentStack::push(TParameterSummaryComponent(_), - SummaryComponentStack::singleton(TArgumentSummaryComponent(_))) and - preservesValue = preservesValue1.booleanAnd(preservesValue2) - ) - } - - /** - * Holds if `c` has a flow summary from `input` to `arg`, where `arg` - * writes to (contents of) arguments at position `pos`, and `c` has a - * value-preserving flow summary from the arguments at position `pos` - * to a return value (`return`). - * - * In such a case, we derive flow from `input` to (contents of) the return - * value. - * - * As an example, this simplifies modeling of fluent methods: - * for `StringBuilder.append(x)` with a specified value flow from qualifier to - * return value and taint flow from argument 0 to the qualifier, then this - * allows us to infer taint flow from argument 0 to the return value. - */ - pragma[nomagic] - private predicate derivedFluentFlow( - SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg, - SummaryComponentStack return, boolean preservesValue - ) { - exists(ParameterPosition pos | - summary(c, input, arg, preservesValue) and - isContentOfArgument(arg, pos) and - summary(c, SummaryComponentStack::argument(pos), return, true) and - return.bottom() = TReturnSummaryComponent(_) + exists(string name | + pos.isKeyword(name) and + result = name + ":" ) - } - - pragma[nomagic] - private predicate derivedFluentFlowPush( - SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg, - SummaryComponent head, SummaryComponentStack tail, int i - ) { - derivedFluentFlow(c, input, arg, tail, _) and - head = arg.drop(i).head() and - i = arg.length() - 2 or - exists(SummaryComponent head0, SummaryComponentStack tail0 | - derivedFluentFlowPush(c, input, arg, head0, tail0, i + 1) and - head = arg.drop(i).head() and - tail = SummaryComponentStack::push(head0, tail0) - ) - } - - private predicate isCallbackParameter(SummaryComponentStack s) { - s.head() = TParameterSummaryComponent(_) and exists(s.tail()) - } - - private predicate isContentOfArgument(SummaryComponentStack s, ParameterPosition pos) { - s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail(), pos) + pos.isSelf() and + result = "self" or - s = SummaryComponentStack::argument(pos) - } - - private predicate outputState(SummarizedCallable c, SummaryComponentStack s) { - summary(c, _, s, _) + pos.isLambdaSelf() and + result = "lambda-self" or - exists(SummaryComponentStack out | - outputState(c, out) and - out.head() = TContentSummaryComponent(_) and - s = out.tail() - ) + pos.isBlock() and + result = "block" or - // Add the argument node corresponding to the requested post-update node - inputState(c, s) and isCallbackParameter(s) + pos.isAny() and + result = "any" + or + pos.isAnyNamed() and + result = "any-named" + or + pos.isHashSplat() and + result = "hash-splat" + or + pos.isSplat(0) and + result = "splat" } - private predicate inputState(SummarizedCallable c, SummaryComponentStack s) { - summary(c, s, _, _) + string encodeArgumentPosition(ArgumentPosition pos) { + pos.isSelf() and result = "self" + or + pos.isLambdaSelf() and result = "lambda-self" or - exists(SummaryComponentStack inp | inputState(c, inp) and s = inp.tail()) + pos.isBlock() and result = "block" or - exists(SummaryComponentStack out | - outputState(c, out) and - out.head() = TParameterSummaryComponent(_) and - s = out.tail() + exists(int i | + pos.isPositional(i) and + result = i.toString() ) or - // Add the post-update node corresponding to the requested argument node - outputState(c, s) and isCallbackParameter(s) + exists(string name | + pos.isKeyword(name) and + result = name + ":" + ) or - // Add the parameter node for parameter side-effects - outputState(c, s) and s = SummaryComponentStack::argument(_) - } - - private newtype TSummaryNodeState = - TSummaryNodeInputState(SummaryComponentStack s) { inputState(_, s) } or - TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) } - - /** - * A state used to break up (complex) flow summaries into atomic flow steps. - * For a flow summary - * - * ```ql - * propagatesFlow( - * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue - * ) - * ``` - * - * the following states are used: - * - * - `TSummaryNodeInputState(SummaryComponentStack s)`: - * this state represents that the components in `s` _have been read_ from the - * input. - * - `TSummaryNodeOutputState(SummaryComponentStack s)`: - * this state represents that the components in `s` _remain to be written_ to - * the output. - */ - private class SummaryNodeState extends TSummaryNodeState { - /** Holds if this state is a valid input state for `c`. */ - pragma[nomagic] - predicate isInputState(SummarizedCallable c, SummaryComponentStack s) { - this = TSummaryNodeInputState(s) and - inputState(c, s) - } - - /** Holds if this state is a valid output state for `c`. */ - pragma[nomagic] - predicate isOutputState(SummarizedCallable c, SummaryComponentStack s) { - this = TSummaryNodeOutputState(s) and - outputState(c, s) - } - - /** Gets a textual representation of this state. */ - string toString() { - exists(SummaryComponentStack s | - this = TSummaryNodeInputState(s) and - result = "read: " + s - ) - or - exists(SummaryComponentStack s | - this = TSummaryNodeOutputState(s) and - result = "to write: " + s - ) - } - } - - private newtype TSummaryNode = - TSummaryInternalNode(SummarizedCallable c, SummaryNodeState state) { - summaryNodeRange(c, state) - } or - TSummaryParameterNode(SummarizedCallable c, ParameterPosition pos) { - summaryParameterNodeRange(c, pos) - } - - abstract class SummaryNode extends TSummaryNode { - abstract string toString(); - - abstract SummarizedCallable getSummarizedCallable(); - } - - private class SummaryInternalNode extends SummaryNode, TSummaryInternalNode { - private SummarizedCallable c; - private SummaryNodeState state; - - SummaryInternalNode() { this = TSummaryInternalNode(c, state) } - - override string toString() { result = "[summary] " + state + " in " + c } - - override SummarizedCallable getSummarizedCallable() { result = c } - } - - private class SummaryParamNode extends SummaryNode, TSummaryParameterNode { - private SummarizedCallable c; - private ParameterPosition pos; - - SummaryParamNode() { this = TSummaryParameterNode(c, pos) } - - override string toString() { result = "[summary param] " + pos + " in " + c } - - override SummarizedCallable getSummarizedCallable() { result = c } - } - - /** - * Holds if `state` represents having read from a parameter at position - * `pos` in `c`. In this case we are not synthesizing a data-flow node, - * but instead assume that a relevant parameter node already exists. - */ - private predicate parameterReadState( - SummarizedCallable c, SummaryNodeState state, ParameterPosition pos - ) { - state.isInputState(c, SummaryComponentStack::argument(pos)) - } - - /** - * Holds if a synthesized summary node is needed for the state `state` in summarized - * callable `c`. - */ - private predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) { - state.isInputState(c, _) and - not parameterReadState(c, state, _) + pos.isAny() and + result = "any" or - state.isOutputState(c, _) + pos.isAnyNamed() and + result = "any-named" } - pragma[noinline] - private SummaryNode summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) { - exists(SummaryNodeState state | state.isInputState(c, s) | - result = TSummaryInternalNode(c, state) + string encodeContent(ContentSet cs, string arg) { + exists(Content c | cs = TSingletonContent(c) | + c = TFieldContent(arg) and result = "Field" or - exists(ParameterPosition pos | - parameterReadState(c, state, pos) and - result = TSummaryParameterNode(c, pos) + exists(ConstantValue cv | + c = TKnownElementContent(cv) and + result = "Element" and + arg = cv.serialize() + "!" ) + or + c = TUnknownElementContent() and result = "Element" and arg = "?" ) - } - - pragma[noinline] - private SummaryNode summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) { - exists(SummaryNodeState state | - state.isOutputState(c, s) and - result = TSummaryInternalNode(c, state) - ) - } - - /** - * Holds if a write targets `post`, which is a post-update node for a - * parameter at position `pos` in `c`. - */ - private predicate isParameterPostUpdate( - SummaryNode post, SummarizedCallable c, ParameterPosition pos - ) { - post = summaryNodeOutputState(c, SummaryComponentStack::argument(pos)) - } - - /** Holds if a parameter node at position `pos` is required for `c`. */ - private predicate summaryParameterNodeRange(SummarizedCallable c, ParameterPosition pos) { - parameterReadState(c, _, pos) or - // Same as `isParameterPostUpdate(_, c, pos)`, but can be used in a negative context - any(SummaryNodeState state).isOutputState(c, SummaryComponentStack::argument(pos)) - } - - private predicate callbackOutput( - SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ReturnKind rk - ) { - any(SummaryNodeState state).isInputState(c, s) and - s.head() = TReturnSummaryComponent(rk) and - receiver = summaryNodeInputState(c, s.tail()) - } - - private predicate callbackInput( - SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ArgumentPosition pos - ) { - any(SummaryNodeState state).isOutputState(c, s) and - s.head() = TParameterSummaryComponent(pos) and - receiver = summaryNodeInputState(c, s.tail()) - } - - /** Holds if a call targeting `receiver` should be synthesized inside `c`. */ - predicate summaryCallbackRange(SummarizedCallable c, SummaryNode receiver) { - callbackOutput(c, _, receiver, _) + cs = TAnyElementContent() and result = "Element" and arg = "any" or - callbackInput(c, _, receiver, _) - } - - /** - * Gets the type of synthesized summary node `n`. - * - * The type is computed based on the language-specific predicates - * `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and - * `getCallbackReturnType()`. - */ - DataFlowType summaryNodeType(SummaryNode n) { - exists(SummaryNode pre | - summaryPostUpdateNode(n, pre) and - result = summaryNodeType(pre) + exists(Content::KnownElementContent kec | + cs = TKnownOrUnknownElementContent(kec) and + result = "Element" and + arg = kec.getIndex().serialize() ) or - exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() | - n = summaryNodeInputState(c, s) and - ( - exists(ContentSet cont | result = getContentType(cont) | - head = TContentSummaryComponent(cont) or - head = TWithContentSummaryComponent(cont) - ) - or - head = TWithoutContentSummaryComponent(_) and - result = summaryNodeType(summaryNodeInputState(c, s.tail())) - or - exists(ReturnKind rk | - head = TReturnSummaryComponent(rk) and - result = - getCallbackReturnType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c), - s.tail())), rk) - ) - or - exists(SummaryComponent::SyntheticGlobal sg | - head = TSyntheticGlobalSummaryComponent(sg) and - result = getSyntheticGlobalType(sg) - ) - or - exists(ParameterPosition pos | - head = TArgumentSummaryComponent(pos) and - result = getParameterType(c, pos) - ) - ) - or - n = summaryNodeOutputState(c, s) and - ( - exists(ContentSet cont | - head = TContentSummaryComponent(cont) and result = getContentType(cont) - ) - or - s.length() = 1 and - exists(ReturnKind rk | - head = TReturnSummaryComponent(rk) and - result = getReturnType(c, rk) - ) - or - exists(ArgumentPosition pos | head = TParameterSummaryComponent(pos) | - result = - getCallbackParameterType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c), - s.tail())), pos) - ) - or - exists(SummaryComponent::SyntheticGlobal sg | - head = TSyntheticGlobalSummaryComponent(sg) and - result = getSyntheticGlobalType(sg) - ) - ) + exists(int lower, boolean includeUnknown, string unknown | + cs = TElementLowerBoundContent(lower, includeUnknown) and + (if includeUnknown = true then unknown = "" else unknown = "!") and + result = "Element" and + arg = lower.toString() + ".." + unknown ) } - /** Holds if summary node `p` is a parameter with position `pos`. */ - predicate summaryParameterNode(SummaryNode p, ParameterPosition pos) { - p = TSummaryParameterNode(_, pos) + string encodeReturn(ReturnKind rk, string arg) { + not rk = Input::getStandardReturnValueKind() and + result = "ReturnValue" and + arg = rk.toString() } - /** Holds if summary node `out` contains output of kind `rk` from call `c`. */ - predicate summaryOutNode(DataFlowCall c, SummaryNode out, ReturnKind rk) { - exists(SummarizedCallable callable, SummaryComponentStack s, SummaryNode receiver | - callbackOutput(callable, s, receiver, rk) and - out = summaryNodeInputState(callable, s) and - c = summaryDataFlowCall(receiver) - ) + string encodeWithoutContent(ContentSet c, string arg) { + result = "Without" + encodeContent(c, arg) } - /** Holds if summary node `arg` is at position `pos` in the call `c`. */ - predicate summaryArgumentNode(DataFlowCall c, SummaryNode arg, ArgumentPosition pos) { - exists(SummarizedCallable callable, SummaryComponentStack s, SummaryNode receiver | - callbackInput(callable, s, receiver, pos) and - arg = summaryNodeOutputState(callable, s) and - c = summaryDataFlowCall(receiver) - ) - } + string encodeWithContent(ContentSet c, string arg) { result = "With" + encodeContent(c, arg) } +} - /** Holds if summary node `post` is a post-update node with pre-update node `pre`. */ - predicate summaryPostUpdateNode(SummaryNode post, SummaryNode pre) { - exists(SummarizedCallable c, ParameterPosition pos | - isParameterPostUpdate(post, c, pos) and - pre = TSummaryParameterNode(c, pos) - ) - or - exists(SummarizedCallable callable, SummaryComponentStack s | - callbackInput(callable, s, _, _) and - pre = summaryNodeOutputState(callable, s) and - post = summaryNodeInputState(callable, s) - ) - } +private import Make as Impl - /** Holds if summary node `ret` is a return node of kind `rk`. */ - predicate summaryReturnNode(SummaryNode ret, ReturnKind rk) { - exists(SummaryComponentStack s | - ret = summaryNodeOutputState(_, s) and - s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk)) - ) +private module StepsInput implements Impl::Private::StepsInputSig { + DataFlowType getSyntheticGlobalType(Private::SyntheticGlobal sg) { + result = TUnknownDataFlowType() and exists(sg) } - /** - * Holds if flow is allowed to pass from parameter `p`, to a return - * node, and back out to `p`. - */ - predicate summaryAllowParameterReturnInSelf(ParamNode p) { - exists(SummarizedCallable c, ParameterPosition ppos | - p.isParameterOf(inject(c), pragma[only_bind_into](ppos)) - | - exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents | - summary(c, inputContents, outputContents, _) and - inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) and - outputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) - ) - ) + DataFlowCall getACall(Public::SummarizedCallable sc) { + result.asCall().getAstNode() = sc.(LibraryCallable).getACall() + or + result.asCall().getAstNode() = sc.(LibraryCallable).getACallSimple() } +} - /** Provides a compilation of flow summaries to atomic data-flow steps. */ - module Steps { - /** - * Holds if there is a local step from `pred` to `succ`, which is synthesized - * from a flow summary. - */ - predicate summaryLocalStep(SummaryNode pred, SummaryNode succ, boolean preservesValue) { - exists( - SummarizedCallable c, SummaryComponentStack inputContents, - SummaryComponentStack outputContents - | - summary(c, inputContents, outputContents, preservesValue) and - pred = summaryNodeInputState(c, inputContents) and - succ = summaryNodeOutputState(c, outputContents) - | - preservesValue = true - or - preservesValue = false and not summary(c, inputContents, outputContents, true) - ) - or - exists(SummarizedCallable c, SummaryComponentStack s | - pred = summaryNodeInputState(c, s.tail()) and - succ = summaryNodeInputState(c, s) and - s.head() = [SummaryComponent::withContent(_), SummaryComponent::withoutContent(_)] and - preservesValue = true - ) - } - - /** - * Holds if there is a read step of content `c` from `pred` to `succ`, which - * is synthesized from a flow summary. - */ - predicate summaryReadStep(SummaryNode pred, ContentSet c, SummaryNode succ) { - exists(SummarizedCallable sc, SummaryComponentStack s | - pred = summaryNodeInputState(sc, s.tail()) and - succ = summaryNodeInputState(sc, s) and - SummaryComponent::content(c) = s.head() - ) - } - - /** - * Holds if there is a store step of content `c` from `pred` to `succ`, which - * is synthesized from a flow summary. - */ - predicate summaryStoreStep(SummaryNode pred, ContentSet c, SummaryNode succ) { - exists(SummarizedCallable sc, SummaryComponentStack s | - pred = summaryNodeOutputState(sc, s) and - succ = summaryNodeOutputState(sc, s.tail()) and - SummaryComponent::content(c) = s.head() - ) - } - - /** - * Holds if there is a jump step from `pred` to `succ`, which is synthesized - * from a flow summary. - */ - predicate summaryJumpStep(SummaryNode pred, SummaryNode succ) { - exists(SummaryComponentStack s | - s = SummaryComponentStack::singleton(SummaryComponent::syntheticGlobal(_)) and - pred = summaryNodeOutputState(_, s) and - succ = summaryNodeInputState(_, s) - ) - } - - /** - * Holds if values stored inside content `c` are cleared at `n`. `n` is a - * synthesized summary node, so in order for values to be cleared at calls - * to the relevant method, it is important that flow does not pass over - * the argument, either via use-use flow or def-use flow. - * - * Example: - * - * ``` - * a.b = taint; - * a.clearB(); // assume we have a flow summary for `clearB` that clears `b` on the qualifier - * sink(a.b); - * ``` - * - * In the above, flow should not pass from `a` on the first line (or the second - * line) to `a` on the third line. Instead, there will be synthesized flow from - * `a` on line 2 to the post-update node for `a` on that line (via an intermediate - * node where field `b` is cleared). - */ - predicate summaryClearsContent(SummaryNode n, ContentSet c) { - exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack | - n = TSummaryInternalNode(sc, state) and - state.isInputState(sc, stack) and - stack.head() = SummaryComponent::withoutContent(c) - ) - } - - /** - * Holds if the value that is being tracked is expected to be stored inside - * content `c` at `n`. - */ - predicate summaryExpectsContent(SummaryNode n, ContentSet c) { - exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack | - n = TSummaryInternalNode(sc, state) and - state.isInputState(sc, stack) and - stack.head() = SummaryComponent::withContent(c) - ) - } - - pragma[noinline] - private predicate viableParam( - DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, SummaryParamNode p - ) { - exists(DataFlowCallable c | - c = inject(sc) and - p = TSummaryParameterNode(sc, ppos) and - c = viableCallable(call) - ) - } - - pragma[nomagic] - private SummaryParamNode summaryArgParam(DataFlowCall call, ArgNode arg, SummarizedCallable sc) { - exists(ParameterPosition ppos | - argumentPositionMatch(call, arg, ppos) and - viableParam(call, sc, ppos, result) - ) - } - - /** - * Holds if `p` can reach `n` in a summarized callable, using only value-preserving - * local steps. `clearsOrExpects` records whether any node on the path from `p` to - * `n` either clears or expects contents. - */ - private predicate paramReachesLocal(SummaryParamNode p, SummaryNode n, boolean clearsOrExpects) { - viableParam(_, _, _, p) and - n = p and - clearsOrExpects = false - or - exists(SummaryNode mid, boolean clearsOrExpectsMid | - paramReachesLocal(p, mid, clearsOrExpectsMid) and - summaryLocalStep(mid, n, true) and - if - summaryClearsContent(n, _) or - summaryExpectsContent(n, _) - then clearsOrExpects = true - else clearsOrExpects = clearsOrExpectsMid - ) - } - - /** - * Holds if use-use flow starting from `arg` should be prohibited. - * - * This is the case when `arg` is the argument of a call that targets a - * flow summary where the corresponding parameter either clears contents - * or expects contents. - */ - pragma[nomagic] - predicate prohibitsUseUseFlow(ArgNode arg, SummarizedCallable sc) { - exists(SummaryParamNode p, ParameterPosition ppos, SummaryNode ret | - paramReachesLocal(p, ret, true) and - p = summaryArgParam(_, arg, sc) and - p = TSummaryParameterNode(_, pragma[only_bind_into](ppos)) and - isParameterPostUpdate(ret, _, pragma[only_bind_into](ppos)) - ) - } - - pragma[nomagic] - private predicate summaryReturnNodeExt(SummaryNode ret, ReturnKindExt rk) { - summaryReturnNode(ret, rk.(ValueReturnKind).getKind()) - or - exists(SummaryParamNode p, SummaryNode pre, ParameterPosition pos | - paramReachesLocal(p, pre, _) and - summaryPostUpdateNode(ret, pre) and - p = TSummaryParameterNode(_, pos) and - rk.(ParamUpdateReturnKind).getPosition() = pos - ) - } - - bindingset[ret] - private SummaryParamNode summaryArgParamRetOut( - ArgNode arg, SummaryNode ret, OutNodeExt out, SummarizedCallable sc - ) { - exists(DataFlowCall call, ReturnKindExt rk | - result = summaryArgParam(call, arg, sc) and - summaryReturnNodeExt(ret, pragma[only_bind_into](rk)) and - out = pragma[only_bind_into](rk).getAnOutNode(call) - ) - } +module Private { + import Impl::Private - /** - * Holds if `arg` flows to `out` using a simple value-preserving flow - * summary, that is, a flow summary without reads and stores. - * - * NOTE: This step should not be used in global data-flow/taint-tracking, but may - * be useful to include in the exposed local data-flow/taint-tracking relations. - */ - predicate summaryThroughStepValue(ArgNode arg, Node out, SummarizedCallable sc) { - exists(ReturnKind rk, SummaryNode ret, DataFlowCall call | - summaryLocalStep(summaryArgParam(call, arg, sc), ret, true) and - summaryReturnNode(ret, pragma[only_bind_into](rk)) and - out = getAnOutNode(call, pragma[only_bind_into](rk)) - ) - } + module Steps = Impl::Private::Steps; +} - /** - * Holds if `arg` flows to `out` using a simple flow summary involving taint - * step, that is, a flow summary without reads and stores. - * - * NOTE: This step should not be used in global data-flow/taint-tracking, but may - * be useful to include in the exposed local data-flow/taint-tracking relations. - */ - predicate summaryThroughStepTaint(ArgNode arg, Node out, SummarizedCallable sc) { - exists(SummaryNode ret | - summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), ret, false) - ) - } +module Public = Impl::Public; - /** - * Holds if there is a read(+taint) of `c` from `arg` to `out` using a - * flow summary. - * - * NOTE: This step should not be used in global data-flow/taint-tracking, but may - * be useful to include in the exposed local data-flow/taint-tracking relations. - */ - predicate summaryGetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) { - exists(SummaryNode mid, SummaryNode ret | - summaryReadStep(summaryArgParamRetOut(arg, ret, out, sc), c, mid) and - summaryLocalStep(mid, ret, _) - ) - } +module ParsePositions { + private import Private - /** - * Holds if there is a (taint+)store of `arg` into content `c` of `out` using a - * flow summary. - * - * NOTE: This step should not be used in global data-flow/taint-tracking, but may - * be useful to include in the exposed local data-flow/taint-tracking relations. - */ - predicate summarySetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) { - exists(SummaryNode mid, SummaryNode ret | - summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), mid, _) and - summaryStoreStep(mid, c, ret) - ) - } + private predicate isParamBody(string body) { + body = any(AccessPathToken tok).getAnArgument("Parameter") } - /** - * Provides a means of translating externally (e.g., MaD) defined flow - * summaries into a `SummarizedCallable`s. - */ - module External { - /** Holds if `spec` is a relevant external specification. */ - private predicate relevantSpec(string spec) { - summaryElement(_, spec, _, _, _) or - summaryElement(_, _, spec, _, _) or - sourceElement(_, spec, _, _) or - sinkElement(_, spec, _, _) - } - - private class AccessPathRange extends AccessPath::Range { - AccessPathRange() { relevantSpec(this) } - } - - /** Holds if specification component `token` parses as parameter `pos`. */ - predicate parseParam(AccessPathToken token, ArgumentPosition pos) { - token.getName() = "Parameter" and - pos = parseParamBody(token.getAnArgument()) - } - - /** Holds if specification component `token` parses as argument `pos`. */ - predicate parseArg(AccessPathToken token, ParameterPosition pos) { - token.getName() = "Argument" and - pos = parseArgBody(token.getAnArgument()) - } - - /** Holds if specification component `token` parses as synthetic global `sg`. */ - predicate parseSynthGlobal(AccessPathToken token, string sg) { - token.getName() = "SyntheticGlobal" and - sg = token.getAnArgument() - } - - private class SyntheticGlobalFromAccessPath extends SummaryComponent::SyntheticGlobal { - SyntheticGlobalFromAccessPath() { parseSynthGlobal(_, this) } - } - - private SummaryComponent interpretComponent(AccessPathToken token) { - exists(ParameterPosition pos | - parseArg(token, pos) and result = SummaryComponent::argument(pos) - ) - or - exists(ArgumentPosition pos | - parseParam(token, pos) and result = SummaryComponent::parameter(pos) - ) - or - token = "ReturnValue" and result = SummaryComponent::return(getReturnValueKind()) - or - exists(string sg | - parseSynthGlobal(token, sg) and result = SummaryComponent::syntheticGlobal(sg) - ) - or - result = interpretComponentSpecific(token) - } - - /** - * Holds if `spec` specifies summary component stack `stack`. - */ - predicate interpretSpec(AccessPath spec, SummaryComponentStack stack) { - interpretSpec(spec, spec.getNumToken(), stack) - } - - /** Holds if the first `n` tokens of `spec` resolves to `stack`. */ - private predicate interpretSpec(AccessPath spec, int n, SummaryComponentStack stack) { - n = 1 and - stack = SummaryComponentStack::singleton(interpretComponent(spec.getToken(0))) - or - exists(SummaryComponent head, SummaryComponentStack tail | - interpretSpec(spec, n, head, tail) and - stack = SummaryComponentStack::push(head, tail) - ) - } - - /** Holds if the first `n` tokens of `spec` resolves to `head` followed by `tail` */ - private predicate interpretSpec( - AccessPath spec, int n, SummaryComponent head, SummaryComponentStack tail - ) { - interpretSpec(spec, n - 1, tail) and - head = interpretComponent(spec.getToken(n - 1)) - } - - private class MkStack extends RequiredSummaryComponentStack { - override predicate required(SummaryComponent head, SummaryComponentStack tail) { - interpretSpec(_, _, head, tail) - } - } - - private class SummarizedCallableExternal extends SummarizedCallable { - SummarizedCallableExternal() { summaryElement(this, _, _, _, _) } - - private predicate relevantSummaryElementGenerated( - AccessPath inSpec, AccessPath outSpec, string kind - ) { - exists(Provenance provenance | - provenance.isGenerated() and - summaryElement(this, inSpec, outSpec, kind, provenance) - ) and - not this.applyManualModel() - } - - private predicate relevantSummaryElement(AccessPath inSpec, AccessPath outSpec, string kind) { - exists(Provenance provenance | - provenance.isManual() and - summaryElement(this, inSpec, outSpec, kind, provenance) - ) - or - this.relevantSummaryElementGenerated(inSpec, outSpec, kind) - } - - override predicate propagatesFlow( - SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue - ) { - exists(AccessPath inSpec, AccessPath outSpec, string kind | - this.relevantSummaryElement(inSpec, outSpec, kind) and - interpretSpec(inSpec, input) and - interpretSpec(outSpec, output) - | - kind = "value" and preservesValue = true - or - kind = "taint" and preservesValue = false - ) - } - - override predicate hasProvenance(Provenance provenance) { - summaryElement(this, _, _, _, provenance) - } - } - - /** Holds if component `c` of specification `spec` cannot be parsed. */ - predicate invalidSpecComponent(AccessPath spec, string c) { - c = spec.getToken(_) and - not exists(interpretComponent(c)) - } - - /** Holds if `provenance` is not a valid provenance value. */ - bindingset[provenance] - predicate invalidProvenance(string provenance) { not provenance instanceof Provenance } - - /** - * Holds if token `part` of specification `spec` has an invalid index. - * E.g., `Argument[-1]`. - */ - predicate invalidIndexComponent(AccessPath spec, AccessPathToken part) { - part = spec.getToken(_) and - part.getName() = ["Parameter", "Argument"] and - AccessPath::parseInt(part.getArgumentList()) < 0 - } - - private predicate inputNeedsReference(AccessPathToken c) { - c.getName() = "Argument" or - inputNeedsReferenceSpecific(c) - } - - private predicate outputNeedsReference(AccessPathToken c) { - c.getName() = ["Argument", "ReturnValue"] or - outputNeedsReferenceSpecific(c) - } - - private predicate sourceElementRef(InterpretNode ref, AccessPath output, string kind) { - exists(SourceOrSinkElement e | - sourceElement(e, output, kind, _) and - if outputNeedsReference(output.getToken(0)) - then e = ref.getCallTarget() - else e = ref.asElement() - ) - } - - private predicate sinkElementRef(InterpretNode ref, AccessPath input, string kind) { - exists(SourceOrSinkElement e | - sinkElement(e, input, kind, _) and - if inputNeedsReference(input.getToken(0)) - then e = ref.getCallTarget() - else e = ref.asElement() - ) - } - - /** Holds if the first `n` tokens of `output` resolve to the given interpretation. */ - private predicate interpretOutput( - AccessPath output, int n, InterpretNode ref, InterpretNode node - ) { - sourceElementRef(ref, output, _) and - n = 0 and - ( - if output = "" - then - // Allow language-specific interpretation of the empty access path - interpretOutputSpecific("", ref, node) - else node = ref - ) - or - exists(InterpretNode mid, AccessPathToken c | - interpretOutput(output, n - 1, ref, mid) and - c = output.getToken(n - 1) - | - exists(ArgumentPosition apos, ParameterPosition ppos | - node.asNode().(PostUpdateNode).getPreUpdateNode().(ArgNode).argumentOf(mid.asCall(), apos) and - parameterMatch(ppos, apos) - | - c = "Argument" or parseArg(c, ppos) - ) - or - exists(ArgumentPosition apos, ParameterPosition ppos | - node.asNode().(ParamNode).isParameterOf(mid.asCallable(), ppos) and - parameterMatch(ppos, apos) - | - c = "Parameter" or parseParam(c, apos) - ) - or - c = "ReturnValue" and - node.asNode() = getAnOutNodeExt(mid.asCall(), TValueReturn(getReturnValueKind())) - or - interpretOutputSpecific(c, mid, node) - ) - } - - /** Holds if the first `n` tokens of `input` resolve to the given interpretation. */ - private predicate interpretInput(AccessPath input, int n, InterpretNode ref, InterpretNode node) { - sinkElementRef(ref, input, _) and - n = 0 and - ( - if input = "" - then - // Allow language-specific interpretation of the empty access path - interpretInputSpecific("", ref, node) - else node = ref - ) - or - exists(InterpretNode mid, AccessPathToken c | - interpretInput(input, n - 1, ref, mid) and - c = input.getToken(n - 1) - | - exists(ArgumentPosition apos, ParameterPosition ppos | - node.asNode().(ArgNode).argumentOf(mid.asCall(), apos) and - parameterMatch(ppos, apos) - | - c = "Argument" or parseArg(c, ppos) - ) - or - exists(ReturnNodeExt ret | - c = "ReturnValue" and - ret = node.asNode() and - ret.getKind().(ValueReturnKind).getKind() = getReturnValueKind() and - mid.asCallable() = getNodeEnclosingCallable(ret) - ) - or - interpretInputSpecific(c, mid, node) - ) - } - - /** - * Holds if `node` is specified as a source with the given kind in a MaD flow - * model. - */ - predicate isSourceNode(InterpretNode node, string kind) { - exists(InterpretNode ref, AccessPath output | - sourceElementRef(ref, output, kind) and - interpretOutput(output, output.getNumToken(), ref, node) - ) - } - - /** - * Holds if `node` is specified as a sink with the given kind in a MaD flow - * model. - */ - predicate isSinkNode(InterpretNode node, string kind) { - exists(InterpretNode ref, AccessPath input | - sinkElementRef(ref, input, kind) and - interpretInput(input, input.getNumToken(), ref, node) - ) - } + private predicate isArgBody(string body) { + body = any(AccessPathToken tok).getAnArgument("Argument") } - /** Provides a query predicate for outputting a set of relevant flow summaries. */ - module TestOutput { - /** A flow summary to include in the `summary/1` query predicate. */ - abstract class RelevantSummarizedCallable instanceof SummarizedCallable { - /** Gets the string representation of this callable used by `summary/1`. */ - abstract string getCallableCsv(); - - /** Holds if flow is propagated between `input` and `output`. */ - predicate relevantSummary( - SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue - ) { - super.propagatesFlow(input, output, preservesValue) - } - - string toString() { result = super.toString() } - } - - /** A model to include in the `neutral/1` query predicate. */ - abstract class RelevantNeutralCallable instanceof NeutralCallable { - /** Gets the string representation of this callable used by `neutral/1`. */ - abstract string getCallableCsv(); - - /** - * Gets the kind of the neutral. - */ - string getKind() { result = super.getKind() } - - string toString() { result = super.toString() } - } - - /** Render the kind in the format used in flow summaries. */ - private string renderKind(boolean preservesValue) { - preservesValue = true and result = "value" - or - preservesValue = false and result = "taint" - } - - private string renderProvenance(SummarizedCallable c) { - if c.applyManualModel() then result = "manual" else c.hasProvenance(result) - } - - private string renderProvenanceNeutral(NeutralCallable c) { - if c.hasManualModel() then result = "manual" else c.hasProvenance(result) - } - - /** - * A query predicate for outputting flow summaries in semi-colon separated format in QL tests. - * The syntax is: "namespace;type;overrides;name;signature;ext;inputspec;outputspec;kind;provenance", - * ext is hardcoded to empty. - */ - query predicate summary(string csv) { - exists( - RelevantSummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output, - boolean preservesValue - | - c.relevantSummary(input, output, preservesValue) and - csv = - c.getCallableCsv() // Callable information - + input.getMadRepresentation() + ";" // input - + output.getMadRepresentation() + ";" // output - + renderKind(preservesValue) + ";" // kind - + renderProvenance(c) // provenance - ) - } - - /** - * Holds if a neutral model `csv` exists (semi-colon separated format). Used for testing purposes. - * The syntax is: "namespace;type;name;signature;kind;provenance"", - */ - query predicate neutral(string csv) { - exists(RelevantNeutralCallable c | - csv = - c.getCallableCsv() // Callable information - + c.getKind() + ";" // kind - + renderProvenanceNeutral(c) // provenance - ) - } + private predicate isElementBody(string body) { + body = any(AccessPathToken tok).getAnArgument(["Element", "WithElement", "WithoutElement"]) } - /** - * Provides query predicates for rendering the generated data flow graph for - * a summarized callable. - * - * Import this module into a `.ql` file of `@kind graph` to render the graph. - * The graph is restricted to callables from `RelevantSummarizedCallable`. - */ - module RenderSummarizedCallable { - /** A summarized callable to include in the graph. */ - abstract class RelevantSummarizedCallable instanceof SummarizedCallable { - string toString() { result = super.toString() } - } - - private newtype TNodeOrCall = - MkNode(SummaryNode n) { - exists(RelevantSummarizedCallable c | - n = TSummaryInternalNode(c, _) - or - n = TSummaryParameterNode(c, _) - ) - } or - MkCall(DataFlowCall call) { - call = summaryDataFlowCall(_) and - call.getEnclosingCallable() = inject(any(RelevantSummarizedCallable c)) - } - - private class NodeOrCall extends TNodeOrCall { - SummaryNode asNode() { this = MkNode(result) } + predicate isParsedParameterPosition(string c, int i) { + isParamBody(c) and + i = AccessPath::parseInt(c) + } - DataFlowCall asCall() { this = MkCall(result) } + predicate isParsedArgumentPosition(string c, int i) { + isArgBody(c) and + i = AccessPath::parseInt(c) + } - string toString() { - result = this.asNode().toString() - or - result = this.asCall().toString() - } + predicate isParsedArgumentLowerBoundPosition(string c, int i) { + isArgBody(c) and + i = AccessPath::parseLowerBound(c) + } - /** - * Holds if this element is at the specified location. - * The location spans column `startcolumn` of line `startline` to - * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see - * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). - */ - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - filepath = "" and - startline = 0 and - startcolumn = 0 and - endline = 0 and - endcolumn = 0 - } - } + predicate isParsedKeywordParameterPosition(string c, string paramName) { + isParamBody(c) and + c = paramName + ":" + } - query predicate nodes(NodeOrCall n, string key, string val) { - key = "semmle.label" and val = n.toString() - } + predicate isParsedKeywordArgumentPosition(string c, string paramName) { + isArgBody(c) and + c = paramName + ":" + } - private predicate edgesComponent(NodeOrCall a, NodeOrCall b, string value) { - exists(boolean preservesValue | - Private::Steps::summaryLocalStep(a.asNode(), b.asNode(), preservesValue) and - if preservesValue = true then value = "value" else value = "taint" - ) - or - exists(ContentSet c | - Private::Steps::summaryReadStep(a.asNode(), c, b.asNode()) and - value = "read (" + c + ")" - or - Private::Steps::summaryStoreStep(a.asNode(), c, b.asNode()) and - value = "store (" + c + ")" - or - Private::Steps::summaryClearsContent(a.asNode(), c) and - b = a and - value = "clear (" + c + ")" - or - Private::Steps::summaryExpectsContent(a.asNode(), c) and - b = a and - value = "expect (" + c + ")" - ) - or - summaryPostUpdateNode(b.asNode(), a.asNode()) and - value = "post-update" - or - b.asCall() = summaryDataFlowCall(a.asNode()) and - value = "receiver" - or - exists(ArgumentPosition pos | - summaryArgumentNode(b.asCall(), a.asNode(), pos) and - value = "argument (" + pos + ")" - ) - } + bindingset[arg] + private string adjustElementArgument(string arg, boolean includeUnknown) { + result = arg.regexpCapture("(.*)!", 1) and + includeUnknown = false + or + result = arg and + not arg.matches("%!") and + includeUnknown = true + } - query predicate edges(NodeOrCall a, NodeOrCall b, string key, string value) { - key = "semmle.label" and - value = strictconcat(string s | edgesComponent(a, b, s) | s, " / ") - } + predicate isParsedElementLowerBoundPosition(string c, boolean includeUnknown, int lower) { + isElementBody(c) and + lower = AccessPath::parseLowerBound(adjustElementArgument(c, includeUnknown)) } } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll deleted file mode 100644 index 9db4f01dfb608..0000000000000 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll +++ /dev/null @@ -1,430 +0,0 @@ -/** - * Provides Ruby specific classes and predicates for defining flow summaries. - */ - -private import codeql.ruby.AST -private import DataFlowDispatch -private import DataFlowPrivate -private import DataFlowPublic -private import DataFlowImplCommon -private import FlowSummaryImpl::Private -private import FlowSummaryImpl::Public -private import codeql.ruby.dataflow.FlowSummary as FlowSummary - -/** - * A class of callables that are candidates for flow summary modeling. - */ -class SummarizedCallableBase = string; - -/** - * A class of callables that are candidates for neutral modeling. - */ -class NeutralCallableBase = string; - -DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c } - -/** Gets the parameter position representing a callback itself, if any. */ -ArgumentPosition callbackSelfParameterPosition() { result.isLambdaSelf() } - -/** Gets the synthesized data-flow call for `receiver`. */ -SummaryCall summaryDataFlowCall(SummaryNode receiver) { receiver = result.getReceiver() } - -/** Gets the type of content `c`. */ -DataFlowType getContentType(ContentSet c) { any() } - -/** Gets the type of the parameter at the given position. */ -DataFlowType getParameterType(SummarizedCallable c, ParameterPosition pos) { any() } - -/** Gets the return type of kind `rk` for callable `c`. */ -bindingset[c, rk] -DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { any() } - -/** - * Gets the type of the `i`th parameter in a synthesized call that targets a - * callback of type `t`. - */ -bindingset[t, pos] -DataFlowType getCallbackParameterType(DataFlowType t, ArgumentPosition pos) { any() } - -/** - * Gets the return type of kind `rk` in a synthesized call that targets a - * callback of type `t`. - */ -DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { any() } - -/** Gets the type of synthetic global `sg`. */ -DataFlowType getSyntheticGlobalType(SummaryComponent::SyntheticGlobal sg) { any() } - -/** - * Holds if an external flow summary exists for `c` with input specification - * `input`, output specification `output`, kind `kind`, and provenance `provenance`. - */ -predicate summaryElement( - FlowSummary::SummarizedCallable c, string input, string output, string kind, string provenance -) { - exists(boolean preservesValue | - c.propagatesFlowExt(input, output, preservesValue) and - (if preservesValue = true then kind = "value" else kind = "taint") and - provenance = "manual" - ) -} - -/** - * Holds if a neutral model exists for `c` of kind `kind` - * and with provenance `provenance`. - * Note. Neutral models have not been implemented for Ruby. - */ -predicate neutralElement(NeutralCallableBase c, string kind, string provenance) { none() } - -bindingset[arg] -private SummaryComponent interpretElementArg(string arg) { - arg = "?" and - result = FlowSummary::SummaryComponent::elementUnknown() - or - arg = "any" and - result = FlowSummary::SummaryComponent::elementAny() - or - exists(int lower, boolean includeUnknown | - ParsePositions::isParsedElementLowerBoundPosition(arg, includeUnknown, lower) - | - includeUnknown = false and - result = FlowSummary::SummaryComponent::elementLowerBound(lower) - or - includeUnknown = true and - result = FlowSummary::SummaryComponent::elementLowerBoundOrUnknown(lower) - ) - or - exists(ConstantValue cv, string argAdjusted, boolean includeUnknown | - argAdjusted = ParsePositions::adjustElementArgument(arg, includeUnknown) and - ( - includeUnknown = false and - result = FlowSummary::SummaryComponent::elementKnown(cv) - or - includeUnknown = true and - result = FlowSummary::SummaryComponent::elementKnownOrUnknown(cv) - ) - | - cv.isInt(AccessPath::parseInt(argAdjusted)) - or - not exists(AccessPath::parseInt(argAdjusted)) and - cv.serialize() = argAdjusted - ) -} - -/** - * Gets the summary component for specification component `c`, if any. - * - * This covers all the Ruby-specific components of a flow summary. - */ -SummaryComponent interpretComponentSpecific(AccessPathToken c) { - exists(string arg, ParameterPosition ppos | - arg = c.getAnArgument("Argument") and - result = FlowSummary::SummaryComponent::argument(ppos) - | - arg = "any" and - ppos.isAny() - or - ppos.isPositionalLowerBound(AccessPath::parseLowerBound(arg)) - or - arg = "hash-splat" and - ppos.isHashSplat() - or - arg = "splat" and - ppos.isSplat(0) - ) - or - result = interpretElementArg(c.getAnArgument("Element")) - or - result = - FlowSummary::SummaryComponent::content(TSingletonContent(TFieldContent(c.getAnArgument("Field")))) - or - exists(ContentSet cs | - FlowSummary::SummaryComponent::content(cs) = interpretElementArg(c.getAnArgument("WithElement")) and - result = FlowSummary::SummaryComponent::withContent(cs) - ) - or - exists(ContentSet cs | - FlowSummary::SummaryComponent::content(cs) = - interpretElementArg(c.getAnArgument("WithoutElement")) and - result = FlowSummary::SummaryComponent::withoutContent(cs) - ) -} - -private string getContentSpecific(Content c) { - exists(string name | c = TFieldContent(name) and result = "Field[" + name + "]") - or - exists(ConstantValue cv | - c = TKnownElementContent(cv) and result = "Element[" + cv.serialize() + "!]" - ) - or - c = TUnknownElementContent() and result = "Element[?]" -} - -private string getContentSetSpecific(ContentSet cs) { - exists(Content c | cs = TSingletonContent(c) and result = getContentSpecific(c)) - or - cs = TAnyElementContent() and result = "Element[any]" - or - exists(Content::KnownElementContent kec | - cs = TKnownOrUnknownElementContent(kec) and - result = "Element[" + kec.getIndex().serialize() + "]" - ) - or - exists(int lower, boolean includeUnknown, string unknown | - cs = TElementLowerBoundContent(lower, includeUnknown) and - (if includeUnknown = true then unknown = "" else unknown = "!") and - result = "Element[" + lower + ".." + unknown + "]" - ) -} - -/** Gets the textual representation of a summary component in the format used for MaD models. */ -string getMadRepresentationSpecific(SummaryComponent sc) { - exists(ContentSet cs | sc = TContentSummaryComponent(cs) and result = getContentSetSpecific(cs)) - or - exists(ContentSet cs | - sc = TWithoutContentSummaryComponent(cs) and - result = "WithoutElement[" + getContentSetSpecific(cs) + "]" - ) - or - exists(ContentSet cs | - sc = TWithContentSummaryComponent(cs) and - result = "WithElement[" + getContentSetSpecific(cs) + "]" - ) - or - exists(ReturnKind rk | - sc = TReturnSummaryComponent(rk) and - not rk = getReturnValueKind() and - result = "ReturnValue[" + rk + "]" - ) -} - -/** Gets the textual representation of a parameter position in the format used for flow summaries. */ -string getParameterPosition(ParameterPosition pos) { - exists(int i | - pos.isPositional(i) and - result = i.toString() - ) - or - exists(int i | - pos.isPositionalLowerBound(i) and - result = i + ".." - ) - or - exists(string name | - pos.isKeyword(name) and - result = name + ":" - ) - or - pos.isSelf() and - result = "self" - or - pos.isLambdaSelf() and - result = "lambda-self" - or - pos.isBlock() and - result = "block" - or - pos.isAny() and - result = "any" - or - pos.isAnyNamed() and - result = "any-named" - or - pos.isHashSplat() and - result = "hash-splat" - or - pos.isSplat(0) and - result = "splat" -} - -/** Gets the textual representation of an argument position in the format used for flow summaries. */ -string getArgumentPosition(ArgumentPosition pos) { - pos.isSelf() and result = "self" - or - pos.isLambdaSelf() and result = "lambda-self" - or - pos.isBlock() and result = "block" - or - exists(int i | - pos.isPositional(i) and - result = i.toString() - ) - or - exists(string name | - pos.isKeyword(name) and - result = name + ":" - ) -} - -/** Holds if input specification component `c` needs a reference. */ -predicate inputNeedsReferenceSpecific(string c) { none() } - -/** Holds if output specification component `c` needs a reference. */ -predicate outputNeedsReferenceSpecific(string c) { none() } - -/** Gets the return kind corresponding to specification `"ReturnValue"`. */ -NormalReturnKind getReturnValueKind() { any() } - -/** - * All definitions in this module are required by the shared implementation - * (for source/sink interpretation), but they are unused for Ruby, where - * we rely on API graphs instead. - */ -private module UnusedSourceSinkInterpretation { - /** - * Holds if an external source specification exists for `n` with output specification - * `output`, kind `kind`, and provenance `provenance`. - */ - predicate sourceElement(AstNode n, string output, string kind, string provenance) { none() } - - /** - * Holds if an external sink specification exists for `n` with input specification - * `input`, kind `kind` and provenance `provenance`. - */ - predicate sinkElement(AstNode n, string input, string kind, string provenance) { none() } - - class SourceOrSinkElement = AstNode; - - /** An entity used to interpret a source/sink specification. */ - class InterpretNode extends AstNode { - /** Gets the element that this node corresponds to, if any. */ - SourceOrSinkElement asElement() { none() } - - /** Gets the data-flow node that this node corresponds to, if any. */ - Node asNode() { none() } - - /** Gets the call that this node corresponds to, if any. */ - DataFlowCall asCall() { none() } - - /** Gets the callable that this node corresponds to, if any. */ - DataFlowCallable asCallable() { none() } - - /** Gets the target of this call, if any. */ - Callable getCallTarget() { none() } - } - - /** Provides additional sink specification logic. */ - predicate interpretOutputSpecific(string c, InterpretNode mid, InterpretNode node) { none() } - - /** Provides additional source specification logic. */ - predicate interpretInputSpecific(string c, InterpretNode mid, InterpretNode node) { none() } -} - -import UnusedSourceSinkInterpretation - -module ParsePositions { - private import FlowSummaryImpl - - private predicate isParamBody(string body) { - body = any(AccessPathToken tok).getAnArgument("Parameter") - } - - private predicate isArgBody(string body) { - body = any(AccessPathToken tok).getAnArgument("Argument") - } - - private predicate isElementBody(string body) { - body = any(AccessPathToken tok).getAnArgument(["Element", "WithElement", "WithoutElement"]) - } - - predicate isParsedParameterPosition(string c, int i) { - isParamBody(c) and - i = AccessPath::parseInt(c) - } - - predicate isParsedArgumentPosition(string c, int i) { - isArgBody(c) and - i = AccessPath::parseInt(c) - } - - predicate isParsedArgumentLowerBoundPosition(string c, int i) { - isArgBody(c) and - i = AccessPath::parseLowerBound(c) - } - - predicate isParsedKeywordParameterPosition(string c, string paramName) { - isParamBody(c) and - c = paramName + ":" - } - - predicate isParsedKeywordArgumentPosition(string c, string paramName) { - isArgBody(c) and - c = paramName + ":" - } - - bindingset[arg] - string adjustElementArgument(string arg, boolean includeUnknown) { - result = arg.regexpCapture("(.*)!", 1) and - includeUnknown = false - or - result = arg and - not arg.matches("%!") and - includeUnknown = true - } - - predicate isParsedElementLowerBoundPosition(string c, boolean includeUnknown, int lower) { - isElementBody(c) and - lower = AccessPath::parseLowerBound(adjustElementArgument(c, includeUnknown)) - } -} - -/** Gets the argument position obtained by parsing `X` in `Parameter[X]`. */ -ArgumentPosition parseParamBody(string s) { - exists(int i | - ParsePositions::isParsedParameterPosition(s, i) and - result.isPositional(i) - ) - or - exists(string name | - ParsePositions::isParsedKeywordParameterPosition(s, name) and - result.isKeyword(name) - ) - or - s = "self" and - result.isSelf() - or - s = "lambda-self" and - result.isLambdaSelf() - or - s = "block" and - result.isBlock() - or - s = "any" and - result.isAny() - or - s = "any-named" and - result.isAnyNamed() -} - -/** Gets the parameter position obtained by parsing `X` in `Argument[X]`. */ -ParameterPosition parseArgBody(string s) { - exists(int i | - ParsePositions::isParsedArgumentPosition(s, i) and - result.isPositional(i) - ) - or - exists(int i | - ParsePositions::isParsedArgumentLowerBoundPosition(s, i) and - result.isPositionalLowerBound(i) - ) - or - exists(string name | - ParsePositions::isParsedKeywordArgumentPosition(s, name) and - result.isKeyword(name) - ) - or - s = "self" and - result.isSelf() - or - s = "lambda-self" and - result.isLambdaSelf() - or - s = "block" and - result.isBlock() - or - s = "any" and - result.isAny() - or - s = "any-named" and - result.isAnyNamed() -} diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll index a687837f8fdf4..06e1400d7994f 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll @@ -541,7 +541,7 @@ private module ParamsSummaries { result = paramsInstance().getAMethodCall(methodReturnsTaintFromSelf()).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and output = "ReturnValue" and preservesValue = false @@ -564,7 +564,7 @@ private module ParamsSummaries { [result.getReceiver(), result.getArgument(0)] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = ["Argument[self]", "Argument[0]"] and output = "ReturnValue" and preservesValue = false @@ -588,7 +588,7 @@ private module ParamsSummaries { [result.getReceiver(), result.getArgument(0)] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = ["Argument[self]", "Argument[0]"] and output = ["ReturnValue", "Argument[self]"] and preservesValue = false diff --git a/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll b/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll index 441c75a81f4e8..880690a1a4b56 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll @@ -61,7 +61,7 @@ module ActiveSupport { ] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and output = "ReturnValue" and preservesValue = false } } @@ -75,7 +75,7 @@ module ActiveSupport { private class IdentitySummary extends SimpleSummarizedCallable { IdentitySummary() { this = ["presence", "deep_dup"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and output = "ReturnValue" and preservesValue = true @@ -109,7 +109,7 @@ module ActiveSupport { private class ToJsonSummary extends SimpleSummarizedCallable { ToJsonSummary() { this = "to_json" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = ["Argument[self]", "Argument[self].Element[any]"] and output = "ReturnValue" and preservesValue = false @@ -124,7 +124,7 @@ module ActiveSupport { private class WithIndifferentAccessSummary extends SimpleSummarizedCallable { WithIndifferentAccessSummary() { this = "with_indifferent_access" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[any]" and preservesValue = true @@ -137,7 +137,7 @@ module ActiveSupport { private class ReverseMergeSummary extends SimpleSummarizedCallable { ReverseMergeSummary() { this = ["reverse_merge", "with_defaults"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self,0].WithElement[any]" and output = "ReturnValue" and preservesValue = true @@ -150,7 +150,7 @@ module ActiveSupport { private class ReverseMergeBangSummary extends SimpleSummarizedCallable { ReverseMergeBangSummary() { this = ["reverse_merge!", "with_defaults!", "reverse_update"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self,0].WithElement[any]" and output = ["ReturnValue", "Argument[self]"] and preservesValue = true @@ -166,7 +166,7 @@ module ActiveSupport { ] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -209,7 +209,7 @@ module ActiveSupport { final override MethodCall getACall() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( exists(string s | s = getExtractComponent(mc, _) | input = "Argument[self].Element[" + s + "!]" and @@ -244,7 +244,7 @@ module ActiveSupport { private class CompactBlankSummary extends SimpleSummarizedCallable { CompactBlankSummary() { this = "compact_blank" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -254,7 +254,7 @@ module ActiveSupport { private class ExcludingSummary extends SimpleSummarizedCallable { ExcludingSummary() { this = ["excluding", "without"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -264,7 +264,7 @@ module ActiveSupport { private class InOrderOfSummary extends SimpleSummarizedCallable { InOrderOfSummary() { this = "in_order_of" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -277,7 +277,7 @@ module ActiveSupport { private class IncludingSummary extends SimpleSummarizedCallable { IncludingSummary() { this = "including" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( exists(ArrayIndex i | input = "Argument[self].Element[" + i + "]" and @@ -299,7 +299,7 @@ module ActiveSupport { private class IndexBySummary extends SimpleSummarizedCallable { IndexBySummary() { this = "index_by" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and preservesValue = true @@ -309,7 +309,7 @@ module ActiveSupport { private class IndexWithSummary extends SimpleSummarizedCallable { IndexWithSummary() { this = "index_with" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -338,7 +338,7 @@ module ActiveSupport { override MethodCall getACall() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[0].Element[" + key + "]" and output = "ReturnValue" and preservesValue = true @@ -369,7 +369,7 @@ module ActiveSupport { override MethodCall getACall() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { exists(string s, int i | s = getKeyArgument(mc, i) and input = "Argument[self].Element[0].Element[" + s + "]" and @@ -392,7 +392,7 @@ module ActiveSupport { override MethodCall getACall() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any].Element[" + key + "]" and output = "ReturnValue.Element[any]" and preservesValue = true @@ -423,7 +423,7 @@ module ActiveSupport { override MethodCall getACall() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { exists(string s, int i | s = getKeyArgument(mc, i) and input = "Argument[self].Element[any].Element[" + s + "]" and @@ -436,7 +436,7 @@ module ActiveSupport { private class SoleSummary extends SimpleSummarizedCallable { SoleSummary() { this = "sole" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[0]" and output = "ReturnValue" and preservesValue = true @@ -470,7 +470,7 @@ module ActiveSupport { private class JsonEscapeSummary extends SimpleSummarizedCallable { JsonEscapeSummary() { this = "json_escape" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Arel.qll b/ruby/ql/lib/codeql/ruby/frameworks/Arel.qll index f57fa41c740c7..92fcb9ac5b4a8 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Arel.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Arel.qll @@ -25,7 +25,7 @@ module Arel { result = API::getTopLevelMember("Arel").getAMethodCall("sql").asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Core.qll b/ruby/ql/lib/codeql/ruby/frameworks/Core.qll index 9835894b82ba3..7711b1f774f58 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Core.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Core.qll @@ -63,7 +63,7 @@ private class SplatSummary extends SummarizedCallable { override SplatExpr getACallSimple() { any() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( // *1 = [1] input = "Argument[self].WithoutElement[any]" and @@ -82,7 +82,7 @@ private class HashSplatSummary extends SummarizedCallable { override HashSplatExpr getACallSimple() { any() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithElement[any]" and output = "ReturnValue" and preservesValue = true diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Erb.qll b/ruby/ql/lib/codeql/ruby/frameworks/Erb.qll index 2d080091b2ba4..d29eda88e4016 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Erb.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Erb.qll @@ -18,7 +18,7 @@ module Erb { override MethodCall getACall() { result = any(ErbTemplateNewCall c).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Files.qll b/ruby/ql/lib/codeql/ruby/frameworks/Files.qll index a23bf3f2ed3d8..b908e3da8f90c 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Files.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Files.qll @@ -115,7 +115,7 @@ module File { result = API::getTopLevelMember("File").getAMethodCall(methodName).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false @@ -133,7 +133,7 @@ module File { result = API::getTopLevelMember("File").getAMethodCall("join").asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0,1..]" and output = "ReturnValue" and preservesValue = false diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Ldap.qll b/ruby/ql/lib/codeql/ruby/frameworks/Ldap.qll index 71186c717fdf4..f3f12bbd55be3 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Ldap.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Ldap.qll @@ -19,7 +19,7 @@ module NetLdap { override MethodCall getACall() { result = any(NetLdapConnection l).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false } } @@ -32,7 +32,7 @@ module NetLdap { override MethodCall getACall() { result = any(NetLdapFilter l).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = ["Argument[0]", "Argument[1]"] and output = "ReturnValue" and preservesValue = false } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll b/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll index 1b7c1cde61e10..efd295c44e7e9 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll @@ -18,7 +18,7 @@ module Mysql2 { override MethodCall getACall() { result = any(Mysql2Connection c).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false } } @@ -66,7 +66,7 @@ module Mysql2 { override MethodCall getACall() { result = any(Mysql2EscapeSanitization c).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Pg.qll b/ruby/ql/lib/codeql/ruby/frameworks/Pg.qll index e0f60730721cc..2c3007cd20bb8 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Pg.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Pg.qll @@ -18,7 +18,7 @@ module Pg { override MethodCall getACall() { result = any(PgConnection c).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll b/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll index 42d038a303dcf..a4de348af32a3 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Rails.qll @@ -314,7 +314,7 @@ private predicate isPotentialRenderCall(MethodCall renderCall, Location loc, Erb // TODO: initialization hooks, e.g. before_configuration, after_initialize... // TODO: initializers /** A synthetic global to represent the value passed to the `locals` argument of a render call for a specific ERB file. */ -private class LocalAssignsHashSyntheticGlobal extends SummaryComponent::SyntheticGlobal { +private class LocalAssignsHashSyntheticGlobal extends string { private ErbFile erbFile; private string id; // Note that we can't use an actual `Rails::RenderCall` here due to problems with non-monotonic recursion @@ -346,7 +346,7 @@ private class RenderLocalsSummary extends SummarizedCallable { override Rails::RenderCall getACall() { result = glob.getARenderCall() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[locals:]" and output = "SyntheticGlobal[" + glob + "]" and preservesValue = true @@ -364,7 +364,7 @@ private class AccessLocalsSummary extends SummarizedCallable { result.getMethodName() = "local_assigns" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "SyntheticGlobal[" + glob + "]" and output = "ReturnValue" and preservesValue = true @@ -394,7 +394,7 @@ private class AccessLocalsKeySummary extends SummarizedCallable { result.getReceiver() instanceof SelfVariableReadAccess } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "SyntheticGlobal[" + glob + "].Element[:" + methodName + "]" and output = "ReturnValue" and preservesValue = true diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Sequel.qll b/ruby/ql/lib/codeql/ruby/frameworks/Sequel.qll index b9488a920165b..65d091e822947 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Sequel.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Sequel.qll @@ -19,7 +19,7 @@ module Sequel { override MethodCall getACall() { result = any(SequelConnection c).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll b/ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll index 01795386a30b2..8c7162eeec8bf 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Sinatra.qll @@ -133,7 +133,7 @@ module Sinatra { /** * A synthetic global representing the hash of local variables passed to an ERB template. */ - class ErbLocalsHashSyntheticGlobal extends SummaryComponent::SyntheticGlobal { + class ErbLocalsHashSyntheticGlobal extends string { private string id; private MethodCall erbCall; private ErbFile erbFile; @@ -172,7 +172,7 @@ module Sinatra { override MethodCall getACall() { result = any(ErbCall c).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[locals:]" and output = "SyntheticGlobal[" + any(ErbLocalsHashSyntheticGlobal global) + "]" and preservesValue = true @@ -207,7 +207,7 @@ module Sinatra { result.getReceiver() instanceof SelfVariableReadAccess } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "SyntheticGlobal[" + global + "].Element[:" + local + "]" and output = "ReturnValue" and preservesValue = true diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll b/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll index 981ace2e7da3d..abb7eec297d38 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll @@ -94,7 +94,7 @@ module Sqlite3 { override MethodCall getACall() { result = any(SQLite3QuoteSanitization c).asExpr().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false } } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Array.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Array.qll index 301b9ba6bf0d6..0e7295b5dd11e 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Array.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Array.qll @@ -46,7 +46,7 @@ module Array { override MethodCall getACallSimple() { result = getAStaticArrayCall("[]") } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // we make use of the special `splat` argument kind, which contains all positional // arguments wrapped in an implicit array, as well as explicit splat arguments input = "Argument[splat]" and @@ -60,7 +60,7 @@ module Array { override MethodCall getACallSimple() { result = getAStaticArrayCall("new") } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[1]" and output = "ReturnValue.Element[?]" @@ -80,7 +80,7 @@ module Array { override MethodCall getACallSimple() { result = getAStaticArrayCall("try_convert") } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0].WithElement[any]" and output = "ReturnValue" and preservesValue = true @@ -92,7 +92,7 @@ module Array { override BitwiseAndExpr getACallSimple() { any() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = ["Argument[self].Element[any]", "Argument[0].Element[any]"] and output = "ReturnValue.Element[?]" and preservesValue = true @@ -104,7 +104,7 @@ module Array { override BitwiseOrExpr getACallSimple() { any() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = ["Argument[self].Element[any]", "Argument[0].Element[any]"] and output = "ReturnValue.Element[?]" and preservesValue = true @@ -116,7 +116,7 @@ module Array { override MulExpr getACallSimple() { any() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -128,7 +128,7 @@ module Array { override AddExpr getACallSimple() { any() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].WithElement[any]" and output = "ReturnValue" @@ -144,7 +144,7 @@ module Array { bindingset[this] DifferenceSummaryShared() { any() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -163,7 +163,7 @@ module Array { override LShiftExpr getACallSimple() { any() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].WithElement[any]" and output = "ReturnValue" @@ -203,7 +203,7 @@ module Array { if methodName = "slice" then index.isInt(_) else any() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[" + index.serialize() + "]" and output = "ReturnValue" and preservesValue = true @@ -240,7 +240,7 @@ module Array { isUnknownElementIndex(mc.getArgument(0)) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue" and preservesValue = true @@ -265,7 +265,7 @@ module Array { this = methodName + "(" + start + ".." + end + ")" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[self].WithElement[?]" and @@ -298,7 +298,7 @@ module Array { ) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[0..]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -325,7 +325,7 @@ module Array { this = "[" + index.serialize() + "]=" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[1]" and output = "Argument[self].Element[" + index.serialize() + "]" and preservesValue = true @@ -344,7 +344,7 @@ module Array { this = "[]=" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[1]" and output = "Argument[self].Element[?]" and preservesValue = true @@ -363,7 +363,7 @@ module Array { ) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // We model this imprecisely, saying that there's flow from any element of // the argument or the receiver to any element of the receiver. This could // be made more precise when the range is known, similar to the way it's @@ -384,7 +384,7 @@ module Array { private class AssocSummary extends SimpleSummarizedCallable { AssocSummary() { this = ["assoc", "rassoc"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any].WithElement[any]" and output = "ReturnValue" and preservesValue = true @@ -409,7 +409,7 @@ module Array { index = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[" + index.serialize() + "]" and output = "ReturnValue" and preservesValue = true @@ -423,7 +423,7 @@ module Array { isUnknownElementIndex(mc.getArgument(0)) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue" and preservesValue = true @@ -433,7 +433,7 @@ module Array { private class BSearchSummary extends SimpleSummarizedCallable { BSearchSummary() { this = "bsearch" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "ReturnValue"] and preservesValue = true @@ -443,7 +443,7 @@ module Array { private class BSearchIndexSummary extends SimpleSummarizedCallable { BSearchIndexSummary() { this = "bsearch_index" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -453,7 +453,7 @@ module Array { private class ClearSummary extends SimpleSummarizedCallable { ClearSummary() { this = "clear" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithoutElement[any]" and output = "Argument[self]" and preservesValue = true @@ -464,7 +464,7 @@ module Array { // `map!` is an alias of `collect!`. CollectBangSummary() { this = ["collect!", "map!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -478,7 +478,7 @@ module Array { private class CombinationSummary extends SimpleSummarizedCallable { CombinationSummary() { this = "combination" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0].Element[?]" @@ -492,7 +492,7 @@ module Array { private class CompactBangSummary extends SimpleSummarizedCallable { CompactBangSummary() { this = "compact!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[0..]" and output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"] and preservesValue = true @@ -502,7 +502,7 @@ module Array { private class ConcatSummary extends SimpleSummarizedCallable { ConcatSummary() { this = "concat" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0..].Element[any]" and output = "Argument[self].Element[?]" and preservesValue = true @@ -512,7 +512,7 @@ module Array { private class DeconstructSummary extends SimpleSummarizedCallable { DeconstructSummary() { this = "deconstruct" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // The documentation of `deconstruct` is blank, but the implementation // shows that it just returns the receiver, unchanged: // https://github.com/ruby/ruby/blob/71bc99900914ef3bc3800a22d9221f5acf528082/array.c#L7810-L7814. @@ -530,7 +530,7 @@ module Array { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].WithoutElement[any]" and output = "Argument[self]" @@ -553,8 +553,8 @@ module Array { mc.getArgument(0).getConstantValue() = index } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or ( ( @@ -586,8 +586,8 @@ module Array { not exists(DataFlow::Content::getKnownElementIndex(mc.getArgument(0))) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or ( // array indices may get shifted @@ -610,7 +610,7 @@ module Array { bindingset[this] DeleteAtSummary() { mc.getMethodName() = "delete_at" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithoutElement[any]" and output = "Argument[self]" and preservesValue = true @@ -628,8 +628,8 @@ module Array { i >= 0 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or ( input = "Argument[self].Element[?]" and @@ -658,8 +658,8 @@ module Array { not mc.getArgument(0).getConstantValue().isInt(_) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or input = "Argument[self].Element[any]" and output = ["ReturnValue", "Argument[self].Element[?]"] and @@ -675,7 +675,7 @@ module Array { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[" + lastBlockParam + "]" @@ -743,7 +743,7 @@ module Array { override MethodCall getACallSimple() { result = dig } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" + buildDigInputSpec(dig) and output = "ReturnValue" and preservesValue = true @@ -764,7 +764,7 @@ module Array { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[" + lastBlockParam + "]" @@ -779,7 +779,7 @@ module Array { private class EachIndexSummary extends SimpleSummarizedCallable { EachIndexSummary() { this = ["each_index", "each_key"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithElement[any]" and output = "ReturnValue" and preservesValue = true @@ -804,7 +804,7 @@ module Array { not index.isInt(any(int i | i < 0)) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[" + index.serialize() + "]" and output = "ReturnValue" @@ -827,7 +827,7 @@ module Array { ) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = ["Argument[self].Element[any]", "Argument[1]"] and output = "ReturnValue" @@ -847,7 +847,7 @@ module Array { override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = ["Argument[0]", "Argument[block].ReturnValue"] and output = "Argument[self].Element[?]" and preservesValue = true @@ -860,8 +860,8 @@ module Array { if exists(mc.getBlock()) then mc.getNumberOfArguments() = 0 else mc.getNumberOfArguments() = 1 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or input = "Argument[self].WithoutElement[any]" and output = "Argument[self]" and @@ -885,7 +885,7 @@ module Array { private class FlattenSummary extends SimpleSummarizedCallable { FlattenSummary() { this = "flatten" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = [ @@ -901,7 +901,7 @@ module Array { private class FlattenBangSummary extends SimpleSummarizedCallable { FlattenBangSummary() { this = "flatten!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = [ @@ -920,7 +920,7 @@ module Array { private class IndexSummary extends SimpleSummarizedCallable { IndexSummary() { this = ["index", "rindex"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -944,7 +944,7 @@ module Array { mc.getArgument(0).getConstantValue().isInt(i) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { exists(int numValues, string r | numValues = mc.getNumberOfArguments() - 1 and r = ["ReturnValue", "Argument[self]"] and @@ -984,7 +984,7 @@ module Array { not mc.getArgument(0).getConstantValue().isInt(_) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" or @@ -1000,7 +1000,7 @@ module Array { IntersectionSummary() { this = "intersection" and mc.getMethodName() = this } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" or @@ -1023,7 +1023,7 @@ module Array { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].WithoutElement[any]" and output = "Argument[self]" @@ -1054,7 +1054,7 @@ module Array { private class LastNoArgSummary extends LastSummary { LastNoArgSummary() { this = "last(no_arg)" and mc.getNumberOfArguments() = 0 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue" and preservesValue = true @@ -1064,7 +1064,7 @@ module Array { private class LastArgSummary extends LastSummary { LastArgSummary() { this = "last(arg)" and mc.getNumberOfArguments() > 0 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -1074,7 +1074,7 @@ module Array { private class PackSummary extends SimpleSummarizedCallable { PackSummary() { this = "pack" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue" and preservesValue = false @@ -1084,7 +1084,7 @@ module Array { private class PermutationSummary extends SimpleSummarizedCallable { PermutationSummary() { this = ["permutation", "repeated_combination", "repeated_permutation"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0].Element[?]" @@ -1111,7 +1111,7 @@ module Array { // We don't track the length of the array, so we can't model that this // clears the last element of the receiver, and we can't be precise about // which particular element flows to the return value. - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue" and preservesValue = true @@ -1124,7 +1124,7 @@ module Array { // We don't track the length of the array, so we can't model that this // clears elements from the end of the receiver, and we can't be precise // about which particular elements flow to the return value. - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -1147,7 +1147,7 @@ module Array { not result.getReceiver().(SelfVariableAccess).getCfgScope() instanceof ModuleBase } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { exists(int num | num = mc.getNumberOfArguments() and preservesValue = true | exists(ArrayIndex i | input = "Argument[self].Element[" + i + "!]" and @@ -1172,7 +1172,7 @@ module Array { private class ProductSummary extends SimpleSummarizedCallable { ProductSummary() { this = "product" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" or @@ -1188,7 +1188,7 @@ module Array { private class JoinSummary extends SimpleSummarizedCallable { JoinSummary() { this = ["join"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue" and preservesValue = false @@ -1199,7 +1199,7 @@ module Array { // `append` is an alias for `push` PushSummary() { this = ["push", "append"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].WithElement[any]" and output = "ReturnValue" @@ -1221,7 +1221,7 @@ module Array { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( // array indices may get shifted input = "Argument[self].Element[0..!]" and @@ -1240,7 +1240,7 @@ module Array { private class ReplaceSummary extends SimpleSummarizedCallable { ReplaceSummary() { this = "replace" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0].WithElement[any]" and output = ["ReturnValue", "Argument[self]"] and preservesValue = true @@ -1254,7 +1254,7 @@ module Array { private class ReverseSummary extends SimpleSummarizedCallable { ReverseSummary() { this = "reverse" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -1264,7 +1264,7 @@ module Array { private class ReverseBangSummary extends SimpleSummarizedCallable { ReverseBangSummary() { this = "reverse!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[self]", "ReturnValue"] + ".Element[?]" and preservesValue = true @@ -1290,7 +1290,7 @@ module Array { not exists(mc.getArgument(0)) and c = 1 and this = "rotate" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[self].Element[?]" and @@ -1315,7 +1315,7 @@ module Array { not DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).isInt(_) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -1330,7 +1330,7 @@ module Array { override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithoutElement[any]" and output = "Argument[self]" and preservesValue = true @@ -1347,8 +1347,8 @@ module Array { not exists(mc.getArgument(0)) and c = 1 and this = "rotate!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or exists(string r | r = ["Argument[self]", "ReturnValue"] and preservesValue = true | input = "Argument[self].Element[?]" and @@ -1373,8 +1373,8 @@ module Array { not mc.getArgument(0).getConstantValue().isInt(_) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or input = "Argument[self].Element[any]" and output = ["Argument[self].Element[?]", "ReturnValue.Element[?]"] and @@ -1395,7 +1395,7 @@ module Array { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[" + lastBlockParam + "]" @@ -1422,7 +1422,7 @@ module Array { override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithoutElement[any]" and output = "Argument[self]" and preservesValue = true @@ -1432,8 +1432,8 @@ module Array { private class ShiftNoArgSummary extends ShiftSummary { ShiftNoArgSummary() { this = "shift" and not exists(mc.getArgument(0)) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or preservesValue = true and ( @@ -1467,8 +1467,8 @@ module Array { this = "shift(" + n + ")" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or preservesValue = true and ( @@ -1495,7 +1495,7 @@ module Array { not mc.getArgument(0).getConstantValue().isInt(_) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[self].Element[?]", "ReturnValue.Element[?]"] and preservesValue = true @@ -1505,7 +1505,7 @@ module Array { private class ShuffleSummary extends SimpleSummarizedCallable { ShuffleSummary() { this = "shuffle" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -1515,7 +1515,7 @@ module Array { private class ShuffleBangSummary extends SimpleSummarizedCallable { ShuffleBangSummary() { this = "shuffle!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["ReturnValue.Element[?]", "Argument[self].Element[?]"] and preservesValue = true @@ -1528,7 +1528,7 @@ module Array { bindingset[this] SliceBangSummary() { mc.getMethodName() = "slice!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithoutElement[any]" and output = "Argument[self]" and preservesValue = true @@ -1547,8 +1547,8 @@ module Array { n = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)).getInt() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or preservesValue = true and ( @@ -1581,8 +1581,8 @@ module Array { isUnknownElementIndex(mc.getArgument(0)) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or input = "Argument[self].Element[any]" and output = @@ -1625,8 +1625,8 @@ module Array { ) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or preservesValue = true and ( @@ -1675,8 +1675,8 @@ module Array { ) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or input = "Argument[self].Element[any]" and output = ["Argument[self].Element[?]", "ReturnValue.Element[?]"] and @@ -1687,7 +1687,7 @@ module Array { private class SortBangSummary extends SimpleSummarizedCallable { SortBangSummary() { this = "sort!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = [ @@ -1705,7 +1705,7 @@ module Array { private class SortByBangSummary extends SimpleSummarizedCallable { SortByBangSummary() { this = "sort_by!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "Argument[self].Element[?]", "ReturnValue.Element[?]"] and @@ -1720,7 +1720,7 @@ module Array { private class TransposeSummary extends SimpleSummarizedCallable { TransposeSummary() { this = "transpose" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and ( input = "Argument[self].Element[?].Element[?]" and @@ -1745,7 +1745,7 @@ module Array { private class UniqBangSummary extends SimpleSummarizedCallable { UniqBangSummary() { this = "uniq!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[self].Element[?]", "ReturnValue.Element[?]", "Argument[block].Parameter[0]"] and @@ -1760,7 +1760,7 @@ module Array { private class UnionSummary extends SimpleSummarizedCallable { UnionSummary() { this = "union" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" or @@ -1798,9 +1798,7 @@ module Array { ) + ")" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) - or + override predicate propagatesFlow(string input, string output, boolean preservesValue) { exists(string s, int i | s = getValuesAtComponent(mc, i) and input = "Argument[self].Element[" + s + "]" and @@ -1816,9 +1814,7 @@ module Array { this = "values_at(?)" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) - or + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -1836,7 +1832,7 @@ module Enumerable { private class ChunkSummary extends SimpleSummarizedCallable { ChunkSummary() { this = "chunk" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -1846,7 +1842,7 @@ module Enumerable { private class ChunkWhileSummary extends SimpleSummarizedCallable { ChunkWhileSummary() { this = "chunk_while" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "Argument[block].Parameter[1]"] and preservesValue = true @@ -1857,7 +1853,7 @@ module Enumerable { // `map` is an alias of `collect`. CollectSummary() { this = ["collect", "map"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -1872,7 +1868,7 @@ module Enumerable { // `flat_map` is an alias of `collect_concat`. CollectConcatSummary() { this = ["collect_concat", "flat_map"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -1886,7 +1882,7 @@ module Enumerable { private class CompactSummary extends SimpleSummarizedCallable { CompactSummary() { this = "compact" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[0..]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -1903,7 +1899,7 @@ module Enumerable { private class CountSummary extends SimpleSummarizedCallable { CountSummary() { this = "count" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -1913,7 +1909,7 @@ module Enumerable { private class CycleSummary extends SimpleSummarizedCallable { CycleSummary() { this = "cycle" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -1924,7 +1920,7 @@ module Enumerable { // `find` is an alias of `detect`. DetectSummary() { this = ["detect", "find"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "ReturnValue"] @@ -1953,7 +1949,7 @@ module Enumerable { mc.getArgument(0).getConstantValue().isInt(i) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[?]" and output = "ReturnValue.Element[?]" @@ -1973,7 +1969,7 @@ module Enumerable { not mc.getArgument(0).getConstantValue().isInt(_) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -1983,7 +1979,7 @@ module Enumerable { private class DropWhileSummary extends SimpleSummarizedCallable { DropWhileSummary() { this = "drop_while" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["ReturnValue.Element[?]", "Argument[block].Parameter[0]"] and preservesValue = true @@ -1993,7 +1989,7 @@ module Enumerable { private class EachConsSummary extends SimpleSummarizedCallable { EachConsSummary() { this = "each_cons" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0].Element[?]" and preservesValue = true @@ -2003,7 +1999,7 @@ module Enumerable { private class EachEntrySummary extends SimpleSummarizedCallable { EachEntrySummary() { this = "each_entry" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" @@ -2018,7 +2014,7 @@ module Enumerable { private class EachSliceSummary extends SimpleSummarizedCallable { EachSliceSummary() { this = "each_slice" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0].Element[?]" @@ -2033,7 +2029,7 @@ module Enumerable { private class EachWithIndexSummary extends SimpleSummarizedCallable { EachWithIndexSummary() { this = "each_with_index" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" @@ -2048,7 +2044,7 @@ module Enumerable { private class EachWithObjectSummary extends SimpleSummarizedCallable { EachWithObjectSummary() { this = "each_with_object" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" @@ -2063,7 +2059,7 @@ module Enumerable { private class FilterMapSummary extends SimpleSummarizedCallable { FilterMapSummary() { this = "filter_map" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -2077,7 +2073,7 @@ module Enumerable { private class FindIndexSummary extends SimpleSummarizedCallable { FindIndexSummary() { this = "find_index" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -2096,7 +2092,7 @@ module Enumerable { private class FirstNoArgSummary extends FirstSummary { FirstNoArgSummary() { this = "first(no_arg)" and mc.getNumberOfArguments() = 0 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[0]" and output = "ReturnValue" and preservesValue = true @@ -2110,7 +2106,7 @@ module Enumerable { this = "first(" + n + ")" and mc.getArgument(0).getConstantValue().isInt(n) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( exists(ArrayIndex i | i < n and @@ -2132,7 +2128,7 @@ module Enumerable { not mc.getArgument(0).getConstantValue().isInt(_) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithElement[any]" and output = "ReturnValue" and preservesValue = true @@ -2156,7 +2152,7 @@ module Enumerable { private class GrepBlockSummary extends GrepSummary { GrepBlockSummary() { this = methodName + "(block)" and exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" @@ -2171,7 +2167,7 @@ module Enumerable { private class GrepNoBlockSummary extends GrepSummary { GrepNoBlockSummary() { this = methodName + "(no_block)" and not exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -2181,7 +2177,7 @@ module Enumerable { private class GroupBySummary extends SimpleSummarizedCallable { GroupBySummary() { this = "group_by" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // TODO: Add flow to return value once we have flow through hashes input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and @@ -2207,7 +2203,7 @@ module Enumerable { private class InjectNoArgSummary extends InjectSummary { InjectNoArgSummary() { this = methodName + "_no_arg" and mc.getNumberOfArguments() = 0 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // The no-argument variant of inject passes element 0 to the first block // parameter (first iteration only). All other elements are passed to the // second block parameter. @@ -2227,7 +2223,7 @@ module Enumerable { private class InjectArgSummary extends InjectSummary { InjectArgSummary() { this = methodName + "_arg" and mc.getNumberOfArguments() > 0 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( // The first argument of the call is passed to the first block parameter. input = "Argument[0]" and @@ -2263,7 +2259,7 @@ module Enumerable { mc.getNumberOfArguments() = 0 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "ReturnValue"] and preservesValue = true @@ -2276,7 +2272,7 @@ module Enumerable { mc.getNumberOfArguments() > 0 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and preservesValue = true @@ -2304,7 +2300,7 @@ module Enumerable { not exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue" and preservesValue = true @@ -2318,7 +2314,7 @@ module Enumerable { not exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -2332,7 +2328,7 @@ module Enumerable { exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "Argument[block].Parameter[1]", "ReturnValue"] and preservesValue = true @@ -2346,7 +2342,7 @@ module Enumerable { exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "Argument[block].Parameter[1]", "ReturnValue.Element[?]"] and @@ -2369,7 +2365,7 @@ module Enumerable { not exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -2382,7 +2378,7 @@ module Enumerable { exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "Argument[block].Parameter[1]", "ReturnValue.Element[?]"] and @@ -2393,7 +2389,7 @@ module Enumerable { private class MinmaxBySummary extends SimpleSummarizedCallable { MinmaxBySummary() { this = "minmax_by" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and preservesValue = true @@ -2403,7 +2399,7 @@ module Enumerable { private class PartitionSummary extends SimpleSummarizedCallable { PartitionSummary() { this = "partition" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?].Element[?]"] and preservesValue = true @@ -2423,7 +2419,7 @@ module Enumerable { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[" + lastBlockParam + "]" and preservesValue = true @@ -2438,7 +2434,7 @@ module Enumerable { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( // array indices may get shifted input = "Argument[self].Element[0..!]" and @@ -2467,7 +2463,7 @@ module Enumerable { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( // array indices may get shifted input = "Argument[self].Element[0..!]" and @@ -2486,7 +2482,7 @@ module Enumerable { private class SliceBeforeAfterSummary extends SimpleSummarizedCallable { SliceBeforeAfterSummary() { this = ["slice_before", "slice_after"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -2496,7 +2492,7 @@ module Enumerable { private class SliceWhenSummary extends SimpleSummarizedCallable { SliceWhenSummary() { this = "slice_when" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "Argument[block].Parameter[1]"] and preservesValue = true @@ -2506,7 +2502,7 @@ module Enumerable { private class SortSummary extends SimpleSummarizedCallable { SortSummary() { this = "sort" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "Argument[block].Parameter[1]", "ReturnValue.Element[?]"] and @@ -2517,7 +2513,7 @@ module Enumerable { private class SortBySummary extends SimpleSummarizedCallable { SortBySummary() { this = "sort_by" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["Argument[block].Parameter[0]", "ReturnValue.Element[?]"] and preservesValue = true @@ -2527,7 +2523,7 @@ module Enumerable { private class SumSummary extends SimpleSummarizedCallable { SumSummary() { this = "sum" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -2551,7 +2547,7 @@ module Enumerable { mc.getArgument(0).getConstantValue().isInt(i) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].WithElement[?]" and output = "ReturnValue" @@ -2571,7 +2567,7 @@ module Enumerable { not mc.getArgument(0).getConstantValue().isInt(_) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // When the index is unknown, we can't know the size of the result, but we // know that indices are preserved, so, as an approximation, we just treat // it like the array is copied. @@ -2584,7 +2580,7 @@ module Enumerable { private class TakeWhileSummary extends SimpleSummarizedCallable { TakeWhileSummary() { this = "take_while" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -2603,7 +2599,7 @@ module Enumerable { // `to_ary` works a bit like `to_a` (close enough for our purposes). ToASummary() { this = ["to_a", "entries", "to_ary"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithElement[0..]" and output = "ReturnValue" and preservesValue = true @@ -2613,7 +2609,7 @@ module Enumerable { private class UniqSummary extends SimpleSummarizedCallable { UniqSummary() { this = "uniq" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = ["ReturnValue.Element[?]", "Argument[block].Parameter[0]"] and preservesValue = true @@ -2632,7 +2628,7 @@ module Enumerable { private class ZipBlockSummary extends ZipSummary { ZipBlockSummary() { this = "zip(block)" and exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0].Element[0]" @@ -2649,7 +2645,7 @@ module Enumerable { private class ZipNoBlockSummary extends ZipSummary { ZipNoBlockSummary() { this = "zip(no_block)" and not exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( // receiver[i] -> return_value[i][0] exists(ArrayIndex i | diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Base64.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Base64.qll index de5bc9845977d..fbf7470847e2b 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Base64.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Base64.qll @@ -17,7 +17,7 @@ private class Base64Decode extends SummarizedCallable { .getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll index 8811d21c9183e..4871d8d992437 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Hash.qll @@ -31,7 +31,7 @@ module Hash { final override MethodCall getACallSimple() { result = getAStaticHashCall("[]") } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // we make use of the special `hash-splat` argument kind, which contains all keyword // arguments wrapped in an implicit hash, as well as explicit hash splat arguments input = "Argument[hash-splat]" and @@ -62,7 +62,7 @@ module Hash { result.getNumberOfArguments() = 1 } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( // Hash[{symbol: x}] input = "Argument[0].WithElement[any]" and @@ -102,7 +102,7 @@ module Hash { exists(result.getArgument(i)) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // Hash[:symbol, x] input = "Argument[" + i + "]" and output = "ReturnValue.Element[" + key.serialize() + "]" and @@ -115,7 +115,7 @@ module Hash { override MethodCall getACallSimple() { result = getAStaticHashCall("try_convert") } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0].WithElement[any]" and output = "ReturnValue" and preservesValue = true @@ -130,7 +130,7 @@ module Hash { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[1]" and output = "ReturnValue" and preservesValue = true @@ -145,8 +145,8 @@ module Hash { this = "store(" + key.serialize() + ")" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or input = "Argument[1]" and output = "Argument[self].Element[" + key.serialize() + "]" and @@ -164,8 +164,8 @@ module Hash { this = "store" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or input = "Argument[1]" and output = "Argument[self].Element[?]" and @@ -192,7 +192,7 @@ module Hash { key = DataFlow::Content::getKnownElementIndex(mc.getArgument(0)) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[" + key.serialize() + "]" and output = "ReturnValue.Element[1]" and preservesValue = true @@ -208,7 +208,7 @@ module Hash { not exists(DataFlow::Content::getKnownElementIndex(result.getArgument(0))) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any].WithoutElement[any]" and output = "ReturnValue.Element[1]" and preservesValue = true @@ -218,7 +218,7 @@ module Hash { private class EachPairSummary extends SimpleSummarizedCallable { EachPairSummary() { this = "each_pair" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[1]" @@ -233,7 +233,7 @@ module Hash { private class EachValueSummary extends SimpleSummarizedCallable { EachValueSummary() { this = "each_value" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" @@ -264,7 +264,7 @@ module Hash { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" + concat(int i, string s | @@ -290,7 +290,7 @@ abstract private class FetchValuesSummary extends SummarizedCallable { final override MethodCall getACallSimple() { result = mc } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].WithElement[?]" and output = "ReturnValue" @@ -314,8 +314,8 @@ private class FetchValuesKnownSummary extends FetchValuesSummary { this = "fetch_values(" + key.serialize() + ")" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or input = "Argument[self].Element[" + key.serialize() + "]" and output = "ReturnValue.Element[?]" and @@ -329,8 +329,8 @@ private class FetchValuesUnknownSummary extends FetchValuesSummary { this = "fetch_values(?)" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { - super.propagatesFlowExt(input, output, preservesValue) + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + super.propagatesFlow(input, output, preservesValue) or input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and @@ -345,7 +345,7 @@ private class MergeSummary extends SimpleSummarizedCallable { this = ["merge", "deep_merge"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self,any].WithElement[any]" and output = "ReturnValue" @@ -364,7 +364,7 @@ private class MergeBangSummary extends SimpleSummarizedCallable { this = ["merge!", "deep_merge!", "update"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self,any].WithElement[any]" and output = ["ReturnValue", "Argument[self]"] @@ -379,7 +379,7 @@ private class MergeBangSummary extends SimpleSummarizedCallable { private class RassocSummary extends SimpleSummarizedCallable { RassocSummary() { this = "rassoc" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any].WithoutElement[any]" and output = "ReturnValue.Element[1]" and preservesValue = true @@ -404,7 +404,7 @@ private class SliceKnownSummary extends SliceSummary { not key.isInt(_) // covered in `Array.qll` } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithElement[" + key.serialize() + "]" and output = "ReturnValue" and preservesValue = true @@ -417,7 +417,7 @@ private class SliceUnknownSummary extends SliceSummary { this = "slice(?)" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithoutElement[0..!].WithElement[any]" and output = "ReturnValue" and preservesValue = true @@ -427,7 +427,7 @@ private class SliceUnknownSummary extends SliceSummary { private class ToASummary extends SimpleSummarizedCallable { ToASummary() { this = "to_a" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithoutElement[0..!].Element[any]" and output = "ReturnValue.Element[?].Element[1]" and preservesValue = true @@ -437,7 +437,7 @@ private class ToASummary extends SimpleSummarizedCallable { private class ToHWithoutBlockSummary extends SimpleSummarizedCallable { ToHWithoutBlockSummary() { this = ["to_h", "to_hash"] and not exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].WithElement[any]" and output = "ReturnValue" and preservesValue = true @@ -447,7 +447,7 @@ private class ToHWithoutBlockSummary extends SimpleSummarizedCallable { private class ToHWithBlockSummary extends SimpleSummarizedCallable { ToHWithBlockSummary() { this = "to_h" and exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[1]" @@ -462,7 +462,7 @@ private class ToHWithBlockSummary extends SimpleSummarizedCallable { private class TransformKeysSummary extends SimpleSummarizedCallable { TransformKeysSummary() { this = "transform_keys" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true @@ -472,7 +472,7 @@ private class TransformKeysSummary extends SimpleSummarizedCallable { private class TransformKeysBangSummary extends SimpleSummarizedCallable { TransformKeysBangSummary() { this = "transform_keys!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[self].Element[?]" @@ -484,7 +484,7 @@ private class TransformKeysBangSummary extends SimpleSummarizedCallable { private class TransformValuesSummary extends SimpleSummarizedCallable { TransformValuesSummary() { this = "transform_values" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" @@ -499,7 +499,7 @@ private class TransformValuesSummary extends SimpleSummarizedCallable { private class TransformValuesBangSummary extends SimpleSummarizedCallable { TransformValuesBangSummary() { this = "transform_values!" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( input = "Argument[self].Element[any]" and output = "Argument[block].Parameter[0]" @@ -517,7 +517,7 @@ private class TransformValuesBangSummary extends SimpleSummarizedCallable { private class ValuesSummary extends SimpleSummarizedCallable { ValuesSummary() { this = "values" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self].Element[any]" and output = "ReturnValue.Element[?]" and preservesValue = true diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Kernel.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Kernel.qll index ad87ee37ecd71..a17bbf9123761 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Kernel.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Kernel.qll @@ -177,7 +177,7 @@ module Kernel { private class TapSummary extends SimpleSummarizedCallable { TapSummary() { this = "tap" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and output = ["ReturnValue", "Argument[block].Parameter[0]"] and preservesValue = true @@ -219,7 +219,7 @@ module Kernel { ) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { ( // already an array input = "Argument[0].WithElement[0..]" and diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/Object.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/Object.qll index 355a65d0c7271..5fbb1b6eff75f 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/Object.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/Object.qll @@ -36,7 +36,7 @@ module Object { private class DupSummary extends SimpleSummarizedCallable { DupSummary() { this = "dup" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and output = "ReturnValue" and preservesValue = true diff --git a/ruby/ql/lib/codeql/ruby/frameworks/core/String.qll b/ruby/ql/lib/codeql/ruby/frameworks/core/String.qll index 8e88eb033e87b..86246ba80a203 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/core/String.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/core/String.qll @@ -127,7 +127,7 @@ module String { result = API::getTopLevelMember("String").getAnInstantiation().getExprNode().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = true @@ -142,7 +142,7 @@ module String { API::getTopLevelMember("String").getAMethodCall("try_convert").getExprNode().getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false @@ -155,7 +155,7 @@ module String { private class FormatSummary extends SimpleSummarizedCallable { FormatSummary() { this = "%" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = ["Argument[self]", "Argument[0]", "Argument[0].Element[any]"] and output = "ReturnValue" and preservesValue = false @@ -169,7 +169,7 @@ module String { private class BSummary extends SimpleSummarizedCallable { BSummary() { this = "b" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -180,7 +180,7 @@ module String { private class BytesliceSummary extends SimpleSummarizedCallable { BytesliceSummary() { this = "byteslice" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -191,7 +191,7 @@ module String { private class CapitalizeSummary extends SimpleSummarizedCallable { CapitalizeSummary() { this = ["capitalize", "capitalize!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and preservesValue = false and output = "ReturnValue" @@ -204,7 +204,7 @@ module String { private class CenterSummary extends SimpleSummarizedCallable { CenterSummary() { this = ["center", "ljust", "rjust"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) or input = "Argument[1]" and @@ -219,7 +219,7 @@ module String { private class ChompSummary extends SimpleSummarizedCallable { ChompSummary() { this = ["chomp", "chomp!", "chop", "chop!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) or this = ["chomp!", "chop!"] and @@ -236,6 +236,10 @@ module String { */ private class ClearSummary extends SimpleSummarizedCallable { ClearSummary() { none() } + + override predicate propagatesFlow(string input, string output, boolean preservesValue) { + none() + } } /** @@ -249,7 +253,7 @@ module String { none() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self,0..]" and output = ["ReturnValue", "Argument[self]"] and preservesValue = false @@ -262,7 +266,7 @@ module String { private class DeleteSummary extends SimpleSummarizedCallable { DeleteSummary() { this = ["delete", "delete_prefix", "delete_suffix"] + ["", "!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -273,7 +277,7 @@ module String { private class DowncaseSummary extends SimpleSummarizedCallable { DowncaseSummary() { this = ["downcase", "upcase", "swapcase"] + ["", "!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -284,7 +288,7 @@ module String { private class DumpSummary extends SimpleSummarizedCallable { DumpSummary() { this = ["dump", "undump"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -308,7 +312,7 @@ module String { private class EachLineBlockSummary extends EachLineSummary { EachLineBlockSummary() { this = "each_line_with_block" and exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = false and input = "Argument[self]" and output = ["Argument[block].Parameter[0]", "ReturnValue"] @@ -321,7 +325,7 @@ module String { private class EachLineNoBlockSummary extends EachLineSummary { EachLineNoBlockSummary() { this = "each_line_without_block" and not exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = false and input = "Argument[self]" and output = "ReturnValue.Element[?]" @@ -334,7 +338,7 @@ module String { private class EncodeSummary extends SimpleSummarizedCallable { EncodeSummary() { this = ["encode", "unicode_normalize"] + ["", "!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -345,7 +349,7 @@ module String { private class ForceEncodingSummary extends SimpleSummarizedCallable { ForceEncodingSummary() { this = "force_encoding" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -356,7 +360,7 @@ module String { private class FreezeSummary extends SimpleSummarizedCallable { FreezeSummary() { this = "freeze" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -370,7 +374,7 @@ module String { // str.gsub(pattern, replacement) -> new_str // str.gsub(pattern) {|match| block } -> new_str // str.gsub(pattern) -> enumerator of matches - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // receiver -> return value // replacement -> return value // block return -> return value @@ -390,7 +394,7 @@ module String { none() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) or input = "Argument[1]" and output = "ReturnValue" and preservesValue = false @@ -403,7 +407,7 @@ module String { private class InspectSummary extends SimpleSummarizedCallable { InspectSummary() { this = "inspect" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -414,7 +418,7 @@ module String { private class StripSummary extends SimpleSummarizedCallable { StripSummary() { this = ["strip", "lstrip", "rstrip"] + ["", "!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -425,7 +429,7 @@ module String { private class NextSummary extends SimpleSummarizedCallable { NextSummary() { this = ["next", "succ"] + ["", "!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -436,7 +440,7 @@ module String { private class PartitionSummary extends SimpleSummarizedCallable { PartitionSummary() { this = ["partition", "rpartition"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and output = "ReturnValue.Element[0,1,2]" and preservesValue = false @@ -449,7 +453,7 @@ module String { private class ReplaceSummary extends SimpleSummarizedCallable { ReplaceSummary() { this = "replace" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = ["ReturnValue", "Argument[self]"] and preservesValue = false @@ -463,7 +467,7 @@ module String { private class ReverseSummary extends SimpleSummarizedCallable { ReverseSummary() { this = ["reverse", "reverse!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -483,7 +487,7 @@ module String { private class ScanBlockSummary extends ScanSummary { ScanBlockSummary() { this = "scan_with_block" and exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and preservesValue = false and output = @@ -500,7 +504,7 @@ module String { private class ScanNoBlockSummary extends ScanSummary { ScanNoBlockSummary() { this = "scan_no_block" and not exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { // scan(pattern) -> array input = "Argument[self]" and output = "ReturnValue.Element[?]" and @@ -523,7 +527,7 @@ module String { private class ScrubBlockSummary extends ScrubSummary { ScrubBlockSummary() { this = "scrub_block" and exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) or preservesValue = false and @@ -542,7 +546,7 @@ module String { private class ScrubNoBlockSummary extends ScrubSummary { ScrubNoBlockSummary() { this = "scrub_no_block" and not exists(mc.getBlock()) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) or preservesValue = false and @@ -557,7 +561,7 @@ module String { private class ShellescapeSummary extends SimpleSummarizedCallable { ShellescapeSummary() { this = "shellescape" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -568,7 +572,7 @@ module String { private class ShellSplitSummary extends SimpleSummarizedCallable { ShellSplitSummary() { this = "shellsplit" } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and output = "ReturnValue.Element[?]" and preservesValue = false @@ -581,7 +585,7 @@ module String { private class SliceSummary extends SimpleSummarizedCallable { SliceSummary() { this = ["slice", "slice!", "split", "[]"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -592,7 +596,7 @@ module String { private class SqueezeSummary extends SimpleSummarizedCallable { SqueezeSummary() { this = ["squeeze", "squeeze!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -603,7 +607,7 @@ module String { private class ToStrSummary extends SimpleSummarizedCallable { ToStrSummary() { this = ["to_str", "to_s"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) } } @@ -614,7 +618,7 @@ module String { private class TrSummary extends SimpleSummarizedCallable { TrSummary() { this = ["tr", "tr_s"] + ["", "!"] } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) or input = "Argument[1]" and output = "ReturnValue" and preservesValue = false @@ -646,7 +650,7 @@ module String { } // TODO: if second arg ('exclusive') is true, the first arg is excluded - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { taintIdentityFlow(input, output, preservesValue) or input = ["Argument[self]", "Argument[0]"] and @@ -668,7 +672,7 @@ module String { mc.getArgument(1).getConstantValue().isBoolean(true) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[self]" and output = "Argument[block].Parameter[0]" and preservesValue = false diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll index 21bc5f69dcb33..5e43dc5249a7e 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/ModelsAsData.qll @@ -48,7 +48,7 @@ private class SummarizedCallableFromModel extends SummarizedCallable { ) } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { exists(string kind | ModelOutput::relevantSummaryModel(type, path, input, output, kind) | kind = "value" and preservesValue = true diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll index 1cb4e1893394f..e76a100263c30 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll @@ -70,8 +70,8 @@ private module API = Specific::API; private module DataFlow = Specific::DataFlow; -private import Specific::AccessPathSyntax private import ApiGraphModelsExtensions as Extensions +import codeql.dataflow.internal.AccessPathSyntax /** Module containing hooks for providing input data to be interpreted as a model. */ module ModelInput { @@ -327,29 +327,29 @@ predicate isRelevantFullPath(string type, string path) { } /** A string from a CSV row that should be parsed as an access path. */ -private class AccessPathRange extends AccessPath::Range { - AccessPathRange() { - isRelevantFullPath(_, this) - or - exists(string type | isRelevantType(type) | - summaryModel(type, _, this, _, _) or - summaryModel(type, _, _, this, _) - ) - or - typeVariableModel(_, this) - } +private predicate accessPathRange(string s) { + isRelevantFullPath(_, s) + or + exists(string type | isRelevantType(type) | + summaryModel(type, _, s, _, _) or + summaryModel(type, _, _, s, _) + ) + or + typeVariableModel(_, s) } +import AccessPath + /** * Gets a successor of `node` in the API graph. */ bindingset[token] -API::Node getSuccessorFromNode(API::Node node, AccessPathToken token) { +API::Node getSuccessorFromNode(API::Node node, AccessPathTokenBase token) { // API graphs use the same label for arguments and parameters. An edge originating from a // use-node represents an argument, and an edge originating from a def-node represents a parameter. // We just map both to the same thing. token.getName() = ["Argument", "Parameter"] and - result = node.getParameter(AccessPath::parseIntUnbounded(token.getAnArgument())) + result = node.getParameter(parseIntUnbounded(token.getAnArgument())) or token.getName() = "ReturnValue" and result = node.getReturn() @@ -362,11 +362,9 @@ API::Node getSuccessorFromNode(API::Node node, AccessPathToken token) { * Gets an API-graph successor for the given invocation. */ bindingset[token] -API::Node getSuccessorFromInvoke(Specific::InvokeNode invoke, AccessPathToken token) { +API::Node getSuccessorFromInvoke(Specific::InvokeNode invoke, AccessPathTokenBase token) { token.getName() = "Argument" and - result = - invoke - .getParameter(AccessPath::parseIntWithArity(token.getAnArgument(), invoke.getNumArgument())) + result = invoke.getParameter(parseIntWithArity(token.getAnArgument(), invoke.getNumArgument())) or token.getName() = "ReturnValue" and result = invoke.getReturn() @@ -378,10 +376,12 @@ API::Node getSuccessorFromInvoke(Specific::InvokeNode invoke, AccessPathToken to /** * Holds if `invoke` invokes a call-site filter given by `token`. */ -pragma[inline] -private predicate invocationMatchesCallSiteFilter(Specific::InvokeNode invoke, AccessPathToken token) { +bindingset[token] +private predicate invocationMatchesCallSiteFilter( + Specific::InvokeNode invoke, AccessPathTokenBase token +) { token.getName() = "WithArity" and - invoke.getNumArgument() = AccessPath::parseIntUnbounded(token.getAnArgument()) + invoke.getNumArgument() = parseIntUnbounded(token.getAnArgument()) or Specific::invocationMatchesExtraCallSiteFilter(invoke, token) } 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 eec603ad78ba3..18cac63ee546c 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -9,9 +9,9 @@ * module API // the API graph module * predicate isPackageUsed(string package) * API::Node getExtraNodeFromPath(string package, string type, string path, int n) - * API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) - * API::Node getExtraSuccessorFromInvoke(InvokeNode node, AccessPathToken token) - * predicate invocationMatchesExtraCallSiteFilter(InvokeNode invoke, AccessPathToken token) + * API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) + * API::Node getExtraSuccessorFromInvoke(InvokeNode node, AccessPathTokenBase token) + * predicate invocationMatchesExtraCallSiteFilter(InvokeNode invoke, AccessPathTokenBase token) * InvokeNode getAnInvocationOf(API::Node node) * predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) * predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) @@ -21,13 +21,11 @@ private import codeql.ruby.AST private import ApiGraphModels +private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl // Re-export libraries needed by ApiGraphModels.qll import codeql.ruby.ApiGraphs -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 FlowSummaryImpl::Public private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch pragma[nomagic] @@ -140,7 +138,7 @@ private predicate methodMatchedByName(AccessPath path, string methodName) { * Gets a Ruby-specific API graph successor of `node` reachable by resolving `token`. */ bindingset[token] -API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) { +API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) { token.getName() = "Member" and result = node.getMember(token.getAnArgument()) or @@ -152,13 +150,13 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) { or token.getName() = "Parameter" and exists(DataFlowDispatch::ArgumentPosition argPos, DataFlowDispatch::ParameterPosition paramPos | - argPos = FlowSummaryImplSpecific::parseParamBody(token.getAnArgument()) and + token.getAnArgument() = FlowSummaryImpl::Input::encodeArgumentPosition(argPos) and DataFlowDispatch::parameterMatch(paramPos, argPos) and result = node.getParameterAtPosition(paramPos) ) or exists(DataFlow::ContentSet contents | - SummaryComponent::content(contents) = FlowSummaryImplSpecific::interpretComponentSpecific(token) and + token.getName() = FlowSummaryImpl::Input::encodeContent(contents, token.getAnArgument()) and result = node.getContents(contents) ) } @@ -167,10 +165,10 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) { * Gets a Ruby-specific API graph successor of `node` reachable by resolving `token`. */ bindingset[token] -API::Node getExtraSuccessorFromInvoke(InvokeNode node, AccessPathToken token) { +API::Node getExtraSuccessorFromInvoke(InvokeNode node, AccessPathTokenBase token) { token.getName() = "Argument" and exists(DataFlowDispatch::ArgumentPosition argPos, DataFlowDispatch::ParameterPosition paramPos | - paramPos = FlowSummaryImplSpecific::parseArgBody(token.getAnArgument()) and + token.getAnArgument() = FlowSummaryImpl::Input::encodeParameterPosition(paramPos) and DataFlowDispatch::parameterMatch(paramPos, argPos) and result = node.getArgumentAtPosition(argPos) ) @@ -199,7 +197,7 @@ API::Node getAFuzzySuccessor(API::Node node) { * Holds if `invoke` matches the Ruby-specific call site filter in `token`. */ bindingset[token] -predicate invocationMatchesExtraCallSiteFilter(InvokeNode invoke, AccessPathToken token) { +predicate invocationMatchesExtraCallSiteFilter(InvokeNode invoke, AccessPathTokenBase token) { token.getName() = "WithBlock" and exists(invoke.getBlock()) or diff --git a/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Utils.qll b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Utils.qll index 4d0b948d650cc..c41c97b006fe9 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Utils.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Utils.qll @@ -22,7 +22,7 @@ module Utils { .getExpr() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = false } } diff --git a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll index 73a4de4c36016..0477a11e93684 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/TypeTrackerSpecific.qll @@ -1,14 +1,13 @@ -private import codeql.ruby.AST as Ast +private import codeql.ruby.AST private import codeql.ruby.CFG as Cfg +private import codeql.ruby.DataFlow private import Cfg::CfgNodes -private import codeql.ruby.dataflow.FlowSummary +private import codeql.ruby.dataflow.FlowSummary as 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.FlowSummaryImpl as FlowSummaryImpl -private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific -private import codeql.ruby.dataflow.internal.AccessPathSyntax class Node = DataFlowPublic::Node; @@ -16,9 +15,9 @@ class TypeTrackingNode = DataFlowPublic::LocalSourceNode; class TypeTrackerContent = DataFlowPublic::ContentSet; -private module SCS = SummaryComponentStack; +private module SCS = FlowSummaryImpl::Private::SummaryComponentStack; -private module SC = SummaryComponent; +private module SC = FlowSummaryImpl::Private::SummaryComponent; /** * An optional content set, that is, a `ContentSet` or the special "no content set" value. @@ -280,10 +279,10 @@ predicate storeStepIntoSourceNode(Node nodeFrom, Node nodeTo, DataFlow::ContentS exists(ExprNodes::MethodCallCfgNode call | contents .isSingleton(DataFlowPublic::Content::getAttributeName(call.getExpr() - .(Ast::SetterMethodCall) + .(SetterMethodCall) .getTargetName())) and nodeTo.(DataFlowPublic::PostUpdateNode).getPreUpdateNode().asExpr() = call.getReceiver() and - call.getExpr() instanceof Ast::SetterMethodCall and + call.getExpr() instanceof SetterMethodCall and call.getArgument(call.getNumberOfArguments() - 1) = nodeFrom.(DataFlowPublic::ExprNode).getExprNode() ) @@ -365,21 +364,21 @@ class Boolean extends boolean { Boolean() { this = true or this = false } } -private import SummaryComponentStack +private import FlowSummaryImpl::Private::SummaryComponentStack /** * Holds if the given component can't be evaluated by `evaluateSummaryComponentStackLocal`. */ pragma[nomagic] -predicate isNonLocal(SummaryComponent component) { +predicate isNonLocal(FlowSummaryImpl::Private::SummaryComponent component) { component = SC::content(_) or component = SC::withContent(_) } private import internal.SummaryTypeTracker as SummaryTypeTracker -private import codeql.ruby.dataflow.FlowSummary as FlowSummary +// private import codeql.ruby.dataflow.FlowSummary as FlowSummary private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input { // Dataflow nodes class Node = DataFlow::Node; @@ -420,37 +419,40 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input { } // Summaries and their stacks - class SummaryComponent = FlowSummary::SummaryComponent; + class SummaryComponent = FlowSummaryImpl::Private::SummaryComponent; - class SummaryComponentStack = FlowSummary::SummaryComponentStack; + class SummaryComponentStack = FlowSummaryImpl::Private::SummaryComponentStack; - predicate singleton = FlowSummary::SummaryComponentStack::singleton/1; + predicate singleton = FlowSummaryImpl::Private::SummaryComponentStack::singleton/1; - predicate push = FlowSummary::SummaryComponentStack::push/2; + predicate push = FlowSummaryImpl::Private::SummaryComponentStack::push/2; // Relating content to summaries - predicate content = FlowSummary::SummaryComponent::content/1; + predicate content = FlowSummaryImpl::Private::SummaryComponent::content/1; - predicate withoutContent = FlowSummary::SummaryComponent::withoutContent/1; + predicate withoutContent = FlowSummaryImpl::Private::SummaryComponent::withoutContent/1; - predicate withContent = FlowSummary::SummaryComponent::withContent/1; + predicate withContent = FlowSummaryImpl::Private::SummaryComponent::withContent/1; - predicate return = FlowSummary::SummaryComponent::return/0; + SummaryComponent return() { + result = + FlowSummaryImpl::Private::SummaryComponent::return(any(DataFlowDispatch::NormalReturnKind k)) + } // Callables - class SummarizedCallable = FlowSummary::SummarizedCallable; + class SummarizedCallable = FlowSummaryImpl::Private::SummarizedCallableImpl; // Relating nodes to summaries - Node argumentOf(Node call, SummaryComponent arg) { + Node argumentOf(Node call, FlowSummaryImpl::Private::SummaryComponent arg) { exists(DataFlowDispatch::ParameterPosition pos | - arg = SummaryComponent::argument(pos) and + arg = FlowSummaryImpl::Private::SummaryComponent::argument(pos) and argumentPositionMatch(call.asExpr(), result, pos) ) } Node parameterOf(Node callable, SummaryComponent param) { exists(DataFlowDispatch::ArgumentPosition apos, DataFlowDispatch::ParameterPosition ppos | - param = SummaryComponent::parameter(apos) and + param = FlowSummaryImpl::Private::SummaryComponent::parameter(apos) and DataFlowDispatch::parameterMatch(ppos, apos) and result .(DataFlowPrivate::ParameterNodeImpl) @@ -459,13 +461,15 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input { } Node returnOf(Node callable, SummaryComponent return) { - return = SummaryComponent::return() and + return = return() and result.(DataFlowPrivate::ReturnNode).(DataFlowPrivate::NodeImpl).getCfgScope() = callable.asExpr().getExpr() } // Relating callables to nodes - Node callTo(SummarizedCallable callable) { result.asExpr().getExpr() = callable.getACallSimple() } + Node callTo(SummarizedCallable callable) { + result.asExpr().getExpr() = callable.(FlowSummary::SummarizedCallable).getACallSimple() + } } private module TypeTrackerSummaryFlow = SummaryTypeTracker::SummaryFlow; diff --git a/ruby/ql/lib/codeql/ruby/typetracking/internal/SummaryTypeTracker.qll b/ruby/ql/lib/codeql/ruby/typetracking/internal/SummaryTypeTracker.qll index 9c6f841651dda..eefb7b07e6ae4 100644 --- a/ruby/ql/lib/codeql/ruby/typetracking/internal/SummaryTypeTracker.qll +++ b/ruby/ql/lib/codeql/ruby/typetracking/internal/SummaryTypeTracker.qll @@ -7,7 +7,9 @@ /** The classes and predicates needed to generate type-tracking steps from summaries. */ signature module Input { // Dataflow nodes - class Node; + class Node { + string toString(); + } // Content class TypeTrackerContent; diff --git a/ruby/ql/test/library-tests/dataflow/api-graphs/VerifyApiGraphExpectations.ql b/ruby/ql/test/library-tests/dataflow/api-graphs/VerifyApiGraphExpectations.ql index 93b5aaf745efe..1fd815c260b7a 100644 --- a/ruby/ql/test/library-tests/dataflow/api-graphs/VerifyApiGraphExpectations.ql +++ b/ruby/ql/test/library-tests/dataflow/api-graphs/VerifyApiGraphExpectations.ql @@ -1,16 +1,15 @@ import ruby +import codeql.dataflow.internal.AccessPathSyntax import codeql.ruby.ast.internal.TreeSitter -import codeql.ruby.dataflow.internal.AccessPathSyntax -import codeql.ruby.frameworks.data.internal.ApiGraphModels +import codeql.ruby.frameworks.data.internal.ApiGraphModels as ApiGraphModels import codeql.ruby.ApiGraphs import TestUtilities.InlineExpectationsTest -class AccessPathFromExpectation extends AccessPath::Range { - AccessPathFromExpectation() { hasExpectationWithValue(_, this) } -} +private predicate accessPathRange(string s) { hasExpectationWithValue(_, s) } + +import AccessPath API::Node evaluatePath(AccessPath path, int n) { - path instanceof AccessPathFromExpectation and n = 1 and exists(AccessPathToken token | token = path.getToken(0) | token.getName() = "Member" and @@ -23,9 +22,9 @@ API::Node evaluatePath(AccessPath path, int n) { result = token.getAnArgument().(API::EntryPoint).getANode() ) or - result = getSuccessorFromNode(evaluatePath(path, n - 1), path.getToken(n - 1)) + result = ApiGraphModels::getSuccessorFromNode(evaluatePath(path, n - 1), path.getToken(n - 1)) or - result = getSuccessorFromInvoke(evaluatePath(path, n - 1), path.getToken(n - 1)) + result = ApiGraphModels::getSuccessorFromInvoke(evaluatePath(path, n - 1), path.getToken(n - 1)) or // TODO this is a workaround, support parsing of Method['[]'] instead path.getToken(n - 1).getName() = "MethodBracket" and diff --git a/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected b/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected index 4352b9471cf85..f8a53dd2601b6 100644 --- a/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected +++ b/ruby/ql/test/library-tests/dataflow/array-flow/type-tracking-array-flow.expected @@ -31,42 +31,16 @@ testFailures | array_flow.rb:594:19:594:47 | # $ SPURIOUS: hasValueFlow=64 | Fixed spurious result:hasValueFlow=64 | | array_flow.rb:595:16:595:34 | # $ hasValueFlow=64 | Missing result:hasValueFlow=64 | | array_flow.rb:596:19:596:47 | # $ SPURIOUS: hasValueFlow=64 | Fixed spurious result:hasValueFlow=64 | -| array_flow.rb:657:10:657:13 | ...[...] | Unexpected result: hasValueFlow=70.1 | -| array_flow.rb:657:10:657:13 | ...[...] | Unexpected result: hasValueFlow=70.2 | -| array_flow.rb:657:10:657:13 | ...[...] | Unexpected result: hasValueFlow=70.3 | -| array_flow.rb:658:10:658:13 | ...[...] | Unexpected result: hasValueFlow=70.1 | -| array_flow.rb:658:10:658:13 | ...[...] | Unexpected result: hasValueFlow=70.3 | | array_flow.rb:659:10:659:13 | ...[...] | Unexpected result: hasValueFlow=70.1 | -| array_flow.rb:659:10:659:13 | ...[...] | Unexpected result: hasValueFlow=70.2 | -| array_flow.rb:660:10:660:13 | ...[...] | Unexpected result: hasValueFlow=70.1 | | array_flow.rb:660:10:660:13 | ...[...] | Unexpected result: hasValueFlow=70.2 | -| array_flow.rb:660:10:660:13 | ...[...] | Unexpected result: hasValueFlow=70.3 | -| array_flow.rb:661:10:661:13 | ...[...] | Unexpected result: hasValueFlow=70.2 | | array_flow.rb:661:10:661:13 | ...[...] | Unexpected result: hasValueFlow=70.3 | -| array_flow.rb:662:10:662:13 | ...[...] | Unexpected result: hasValueFlow=70.1 | -| array_flow.rb:662:10:662:13 | ...[...] | Unexpected result: hasValueFlow=70.2 | -| array_flow.rb:662:10:662:13 | ...[...] | Unexpected result: hasValueFlow=70.3 | -| array_flow.rb:663:10:663:13 | ...[...] | Unexpected result: hasValueFlow=70.1 | -| array_flow.rb:663:10:663:13 | ...[...] | Unexpected result: hasValueFlow=70.3 | | array_flow.rb:664:10:664:13 | ...[...] | Unexpected result: hasValueFlow=70.1 | -| array_flow.rb:664:10:664:13 | ...[...] | Unexpected result: hasValueFlow=70.2 | -| array_flow.rb:665:10:665:13 | ...[...] | Unexpected result: hasValueFlow=70.1 | | array_flow.rb:665:10:665:13 | ...[...] | Unexpected result: hasValueFlow=70.2 | -| array_flow.rb:665:10:665:13 | ...[...] | Unexpected result: hasValueFlow=70.3 | -| array_flow.rb:666:10:666:13 | ...[...] | Unexpected result: hasValueFlow=70.2 | | array_flow.rb:666:10:666:13 | ...[...] | Unexpected result: hasValueFlow=70.3 | | array_flow.rb:719:14:719:14 | x | Unexpected result: hasValueFlow=76.2 | | array_flow.rb:871:18:871:36 | # $ hasValueFlow=87 | Missing result:hasValueFlow=87 | | array_flow.rb:872:18:872:36 | # $ hasValueFlow=87 | Missing result:hasValueFlow=87 | -| array_flow.rb:926:10:926:13 | ...[...] | Unexpected result: hasValueFlow=90.1 | -| array_flow.rb:926:10:926:13 | ...[...] | Unexpected result: hasValueFlow=90.2 | -| array_flow.rb:927:10:927:13 | ...[...] | Unexpected result: hasValueFlow=90.1 | -| array_flow.rb:927:10:927:13 | ...[...] | Unexpected result: hasValueFlow=90.2 | | array_flow.rb:928:10:928:13 | ...[...] | Unexpected result: hasValueFlow=90.1 | -| array_flow.rb:929:10:929:13 | ...[...] | Unexpected result: hasValueFlow=90.1 | -| array_flow.rb:929:10:929:13 | ...[...] | Unexpected result: hasValueFlow=90.2 | -| array_flow.rb:930:10:930:13 | ...[...] | Unexpected result: hasValueFlow=90.1 | -| array_flow.rb:930:10:930:13 | ...[...] | Unexpected result: hasValueFlow=90.2 | | array_flow.rb:931:10:931:13 | ...[...] | Unexpected result: hasValueFlow=90.2 | | array_flow.rb:939:18:939:78 | # $ hasValueFlow=91.1 $ hasValueFlow=91.2 $ hasValueFlow=91.3 | Missing result:hasValueFlow=91.1 | | array_flow.rb:939:18:939:78 | # $ hasValueFlow=91.1 $ hasValueFlow=91.2 $ hasValueFlow=91.3 | Missing result:hasValueFlow=91.2 | @@ -143,15 +117,7 @@ testFailures | array_flow.rb:1512:18:1512:39 | # $ hasValueFlow=128.1 | Missing result:hasValueFlow=128.1 | | array_flow.rb:1513:18:1513:39 | # $ hasValueFlow=128.2 | Missing result:hasValueFlow=128.2 | | array_flow.rb:1514:18:1514:39 | # $ hasValueFlow=128.3 | Missing result:hasValueFlow=128.3 | -| array_flow.rb:1563:10:1563:13 | ...[...] | Unexpected result: hasValueFlow=132.1 | -| array_flow.rb:1563:10:1563:13 | ...[...] | Unexpected result: hasValueFlow=132.2 | -| array_flow.rb:1564:10:1564:13 | ...[...] | Unexpected result: hasValueFlow=132.1 | -| array_flow.rb:1564:10:1564:13 | ...[...] | Unexpected result: hasValueFlow=132.2 | | array_flow.rb:1565:10:1565:13 | ...[...] | Unexpected result: hasValueFlow=132.1 | -| array_flow.rb:1566:10:1566:13 | ...[...] | Unexpected result: hasValueFlow=132.1 | -| array_flow.rb:1566:10:1566:13 | ...[...] | Unexpected result: hasValueFlow=132.2 | -| array_flow.rb:1567:10:1567:13 | ...[...] | Unexpected result: hasValueFlow=132.1 | -| array_flow.rb:1567:10:1567:13 | ...[...] | Unexpected result: hasValueFlow=132.2 | | array_flow.rb:1568:10:1568:13 | ...[...] | Unexpected result: hasValueFlow=132.2 | | array_flow.rb:1601:18:1601:39 | # $ hasValueFlow=134.3 | Missing result:hasValueFlow=134.3 | | array_flow.rb:1602:18:1602:39 | # $ hasValueFlow=134.2 | Missing result:hasValueFlow=134.2 | diff --git a/ruby/ql/test/library-tests/dataflow/flow-summaries/semantics.ql b/ruby/ql/test/library-tests/dataflow/flow-summaries/semantics.ql index 158b544c6f7ab..455ed97053894 100644 --- a/ruby/ql/test/library-tests/dataflow/flow-summaries/semantics.ql +++ b/ruby/ql/test/library-tests/dataflow/flow-summaries/semantics.ql @@ -16,7 +16,7 @@ abstract private class Summary extends SimpleSummarizedCallable { bindingset[this] Summary() { any() } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { this.propagates(input, output) and preservesValue = true } diff --git a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql index 89dce373b3203..c65482999e26d 100644 --- a/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql +++ b/ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql @@ -7,13 +7,12 @@ import codeql.ruby.ApiGraphs import codeql.ruby.dataflow.FlowSummary import codeql.ruby.TaintTracking import codeql.ruby.dataflow.internal.FlowSummaryImpl -import codeql.ruby.dataflow.internal.AccessPathSyntax import codeql.ruby.frameworks.data.ModelsAsData import TestUtilities.InlineFlowTest import PathGraph query predicate invalidSpecComponent(SummarizedCallable sc, string s, string c) { - (sc.propagatesFlowExt(s, _, _) or sc.propagatesFlowExt(_, s, _)) and + (sc.propagatesFlow(s, _, _) or sc.propagatesFlow(_, s, _)) and Private::External::invalidSpecComponent(s, c) } @@ -24,7 +23,7 @@ private class SummarizedCallableIdentity extends SummarizedCallable { override MethodCall getACall() { result.getMethodName() = this } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "ReturnValue" and preservesValue = true @@ -36,7 +35,7 @@ private class SummarizedCallableApplyBlock extends SummarizedCallable { override MethodCall getACall() { result.getMethodName() = this } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[0]" and output = "Argument[block].Parameter[0]" and preservesValue = true @@ -52,7 +51,7 @@ private class SummarizedCallableApplyLambda extends SummarizedCallable { override MethodCall getACall() { result.getMethodName() = this } - override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + override predicate propagatesFlow(string input, string output, boolean preservesValue) { input = "Argument[1]" and output = "Argument[0].Parameter[0]" and preservesValue = true diff --git a/shared/dataflow/codeql/dataflow/DataFlow.qll b/shared/dataflow/codeql/dataflow/DataFlow.qll index f7a2429ee8acc..f89a6ccfa15ee 100644 --- a/shared/dataflow/codeql/dataflow/DataFlow.qll +++ b/shared/dataflow/codeql/dataflow/DataFlow.qll @@ -117,6 +117,9 @@ signature module InputSig { * stored into (`getAStoreContent`) or read from (`getAReadContent`). */ class ContentSet { + /** Gets a textual representation of this element. */ + string toString(); + /** Gets a content that may be stored into when storing into this set. */ Content getAStoreContent(); diff --git a/shared/dataflow/codeql/dataflow/internal/AccessPathSyntax.qll b/shared/dataflow/codeql/dataflow/internal/AccessPathSyntax.qll index 0c3dc8427b2b1..8c7d5b1e07272 100644 --- a/shared/dataflow/codeql/dataflow/internal/AccessPathSyntax.qll +++ b/shared/dataflow/codeql/dataflow/internal/AccessPathSyntax.qll @@ -15,35 +15,69 @@ private predicate regexpCaptureTwo(string input, string regexp, string capture1, capture2 = input.regexpCapture(regexp, 2) } -/** Companion module to the `AccessPath` class. */ -module AccessPath { - /** A string that should be parsed as an access path. */ - abstract class Range extends string { - bindingset[this] - Range() { any() } - } +/** + * Parses an integer constant `n` or interval `n1..n2` (inclusive) and gets the value + * of the constant or any value contained in the interval. + */ +bindingset[arg] +int parseInt(string arg) { + result = arg.toInt() + or + // Match "n1..n2" + exists(string lo, string hi | + regexpCaptureTwo(arg, "(-?\\d+)\\.\\.(-?\\d+)", lo, hi) and + result = [lo.toInt() .. hi.toInt()] + ) +} - /** - * Parses an integer constant `n` or interval `n1..n2` (inclusive) and gets the value - * of the constant or any value contained in the interval. - */ - bindingset[arg] - int parseInt(string arg) { - result = arg.toInt() - or - // Match "n1..n2" - exists(string lo, string hi | - regexpCaptureTwo(arg, "(-?\\d+)\\.\\.(-?\\d+)", lo, hi) and - result = [lo.toInt() .. hi.toInt()] - ) +/** + * Parses a lower-bounded interval `n..` and gets the lower bound. + */ +bindingset[arg] +int parseLowerBound(string arg) { result = arg.regexpCapture("(-?\\d+)\\.\\.", 1).toInt() } + +/** + * An access part token such as `Argument[1]` or `ReturnValue`. + */ +class AccessPathTokenBase extends string { + bindingset[this] + AccessPathTokenBase() { exists(this) } + + bindingset[this] + private string getPart(int part) { + result = this.regexpCapture("([^\\[]+)(?:\\[([^\\]]*)\\])?", part) } + /** Gets the name of the token, such as `Member` from `Member[x]` */ + bindingset[this] + string getName() { result = this.getPart(1) } + /** - * Parses a lower-bounded interval `n..` and gets the lower bound. + * Gets the argument list, such as `1,2` from `Member[1,2]`, + * or has no result if there are no arguments. */ - bindingset[arg] - int parseLowerBound(string arg) { result = arg.regexpCapture("(-?\\d+)\\.\\.", 1).toInt() } + bindingset[this] + string getArgumentList() { result = this.getPart(2) } + + /** Gets the `n`th argument to this token, such as `x` or `y` from `Member[x,y]`. */ + bindingset[this] + string getArgument(int n) { result = this.getArgumentList().splitAt(",", n).trim() } + + /** Gets an argument to this token, such as `x` or `y` from `Member[x,y]`. */ + bindingset[this] + string getAnArgument() { result = this.getArgument(_) } + + /** Gets the number of arguments to this token, such as 2 for `Member[x,y]` or zero for `ReturnValue`. */ + bindingset[this] + int getNumArgument() { result = count(int n | exists(this.getArgument(n))) } +} + +final private class AccessPathTokenBaseFinal = AccessPathTokenBase; + +signature predicate accessPathRangeSig(string s); +/** Companion module to the `AccessPath` class. */ +module AccessPath { /** * Parses an integer constant or interval (bounded or unbounded) that explicitly * references the arity, such as `N-1` or `N-3..N-1`. @@ -109,74 +143,78 @@ module AccessPath { or result = parseIntWithExplicitArity(arg, arity) } -} - -/** Gets the `n`th token on the access path as a string. */ -private string getRawToken(AccessPath path, int n) { - // Avoid splitting by '.' since tokens may contain dots, e.g. `Field[foo.Bar.x]`. - // Instead use regexpFind to match valid tokens, and supplement with a final length - // check (in `AccessPath.hasSyntaxError`) to ensure all characters were included in a token. - result = path.regexpFind("\\w+(?:\\[[^\\]]*\\])?(?=\\.|$)", n, _) -} - -/** - * A string that occurs as an access path (either identifying or input/output spec) - * which might be relevant for this database. - */ -class AccessPath extends string instanceof AccessPath::Range { - /** Holds if this string is not a syntactically valid access path. */ - predicate hasSyntaxError() { - // If the lengths match, all characters must haven been included in a token - // or seen by the `.` lookahead pattern. - this != "" and - not this.length() = sum(int n | | getRawToken(this, n).length() + 1) - 1 - } - - /** Gets the `n`th token on the access path (if there are no syntax errors). */ - AccessPathToken getToken(int n) { - result = getRawToken(this, n) and - not this.hasSyntaxError() - } - /** Gets the number of tokens on the path (if there are no syntax errors). */ - int getNumToken() { - result = count(int n | exists(getRawToken(this, n))) and - not this.hasSyntaxError() + /** Gets the `n`th token on the access path as a string. */ + private string getRawToken(AccessPath path, int n) { + // Avoid splitting by '.' since tokens may contain dots, e.g. `Field[foo.Bar.x]`. + // Instead use regexpFind to match valid tokens, and supplement with a final length + // check (in `AccessPath.hasSyntaxError`) to ensure all characters were included in a token. + result = path.regexpFind("\\w+(?:\\[[^\\]]*\\])?(?=\\.|$)", n, _) } -} - -/** - * An access part token such as `Argument[1]` or `ReturnValue`, appearing in one or more access paths. - */ -class AccessPathToken extends string { - AccessPathToken() { this = getRawToken(_, _) } - private string getPart(int part) { - result = this.regexpCapture("([^\\[]+)(?:\\[([^\\]]*)\\])?", part) + /** + * A string that occurs as an access path (either identifying or input/output spec) + * which might be relevant for this database. + */ + final class AccessPath extends string { + AccessPath() { accessPathRange(this) } + + /** Holds if this string is not a syntactically valid access path. */ + predicate hasSyntaxError() { + // If the lengths match, all characters must haven been included in a token + // or seen by the `.` lookahead pattern. + this != "" and + not this.length() = sum(int n | | getRawToken(this, n).length() + 1) - 1 + } + + /** Gets the `n`th token on the access path (if there are no syntax errors). */ + AccessPathToken getToken(int n) { + result = getRawToken(this, n) and + not this.hasSyntaxError() + } + + /** Gets the number of tokens on the path (if there are no syntax errors). */ + int getNumToken() { + result = count(int n | exists(getRawToken(this, n))) and + not this.hasSyntaxError() + } } - /** Gets the name of the token, such as `Member` from `Member[x]` */ - string getName() { result = this.getPart(1) } - /** - * Gets the argument list, such as `1,2` from `Member[1,2]`, - * or has no result if there are no arguments. + * An access part token such as `Argument[1]` or `ReturnValue`, appearing in one or more access paths. */ - string getArgumentList() { result = this.getPart(2) } - - /** Gets the `n`th argument to this token, such as `x` or `y` from `Member[x,y]`. */ - string getArgument(int n) { result = this.getArgumentList().splitAt(",", n).trim() } - - /** Gets the `n`th argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */ - pragma[nomagic] - string getArgument(string name, int n) { name = this.getName() and result = this.getArgument(n) } - - /** Gets an argument to this token, such as `x` or `y` from `Member[x,y]`. */ - string getAnArgument() { result = this.getArgument(_) } - - /** Gets an argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */ - string getAnArgument(string name) { result = this.getArgument(name, _) } - - /** Gets the number of arguments to this token, such as 2 for `Member[x,y]` or zero for `ReturnValue`. */ - int getNumArgument() { result = count(int n | exists(this.getArgument(n))) } + class AccessPathToken extends AccessPathTokenBaseFinal { + AccessPathToken() { this = getRawToken(_, _) } + + /** Gets the name of the token, such as `Member` from `Member[x]` */ + pragma[nomagic] + string getName() { result = super.getName() } + + /** + * Gets the argument list, such as `1,2` from `Member[1,2]`, + * or has no result if there are no arguments. + */ + pragma[nomagic] + string getArgumentList() { result = super.getArgumentList() } + + /** Gets the `n`th argument to this token, such as `x` or `y` from `Member[x,y]`. */ + pragma[nomagic] + string getArgument(int n) { result = super.getArgument(n) } + + /** Gets the `n`th argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */ + pragma[nomagic] + string getArgument(string name, int n) { + name = this.getName() and result = this.getArgument(n) + } + + /** Gets an argument to this token, such as `x` or `y` from `Member[x,y]`. */ + string getAnArgument() { result = this.getArgument(_) } + + /** Gets an argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */ + string getAnArgument(string name) { result = this.getArgument(name, _) } + + /** Gets the number of arguments to this token, such as 2 for `Member[x,y]` or zero for `ReturnValue`. */ + pragma[nomagic] + int getNumArgument() { result = count(int n | exists(this.getArgument(n))) } + } } diff --git a/shared/dataflow/codeql/dataflow/internal/FlowSummaryImpl.qll b/shared/dataflow/codeql/dataflow/internal/FlowSummaryImpl.qll index 0aa17c521b43f..9451b0f88c402 100644 --- a/shared/dataflow/codeql/dataflow/internal/FlowSummaryImpl.qll +++ b/shared/dataflow/codeql/dataflow/internal/FlowSummaryImpl.qll @@ -1,1491 +1,1735 @@ /** * Provides classes and predicates for defining flow summaries. - * - * The definitions in this file are language-independent, and language-specific - * definitions are passed in via the `DataFlowImplSpecific` and - * `FlowSummaryImplSpecific` modules. */ -private import FlowSummaryImplSpecific -private import DataFlowImplSpecific::Private -private import DataFlowImplSpecific::Public -private import DataFlowImplCommon -private import codeql.util.Unit - -/** Provides classes and predicates for defining flow summaries. */ -module Public { - private import Private +private import codeql.dataflow.DataFlow as DF +private import DataFlowImpl +/** + * Provides language-specific parameters. + */ +signature module InputSig { /** - * A component used in a flow summary. - * - * Either a parameter or an argument at a given position, a specific - * content type, or a return kind. + * A base class of callables that are candidates for flow summary modeling. */ - class SummaryComponent extends TSummaryComponent { - /** Gets a textual representation of this component used for MaD models. */ - string getMadRepresentation() { - result = getMadRepresentationSpecific(this) - or - exists(ArgumentPosition pos | - this = TParameterSummaryComponent(pos) and - result = "Parameter[" + getArgumentPosition(pos) + "]" - ) - or - exists(ParameterPosition pos | - this = TArgumentSummaryComponent(pos) and - result = "Argument[" + getParameterPosition(pos) + "]" - ) - or - exists(string synthetic | - this = TSyntheticGlobalSummaryComponent(synthetic) and - result = "SyntheticGlobal[" + synthetic + "]" - ) - or - this = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue" - } - - /** Gets a textual representation of this summary component. */ - string toString() { result = this.getMadRepresentation() } + bindingset[this] + class SummarizedCallableBase { + bindingset[this] + string toString(); } - /** Provides predicates for constructing summary components. */ - module SummaryComponent { - /** Gets a summary component for content `c`. */ - SummaryComponent content(ContentSet c) { result = TContentSummaryComponent(c) } + /** Gets the parameter position representing a callback itself, if any. */ + Lang::ArgumentPosition callbackSelfParameterPosition(); - /** Gets a summary component where data is not allowed to be stored in `c`. */ - SummaryComponent withoutContent(ContentSet c) { result = TWithoutContentSummaryComponent(c) } + /** Gets the type of content `c`. */ + Lang::DataFlowType getContentType(Lang::ContentSet c); - /** Gets a summary component where data must be stored in `c`. */ - SummaryComponent withContent(ContentSet c) { result = TWithContentSummaryComponent(c) } + /** Gets the type of the parameter at the given position. */ + bindingset[c, pos] + Lang::DataFlowType getParameterType(SummarizedCallableBase c, Lang::ParameterPosition pos); - /** Gets a summary component for a parameter at position `pos`. */ - SummaryComponent parameter(ArgumentPosition pos) { result = TParameterSummaryComponent(pos) } + /** Gets the return type of kind `rk` for callable `c`. */ + bindingset[c, rk] + Lang::DataFlowType getReturnType(SummarizedCallableBase c, Lang::ReturnKind rk); - /** Gets a summary component for an argument at position `pos`. */ - SummaryComponent argument(ParameterPosition pos) { result = TArgumentSummaryComponent(pos) } + /** + * Gets the type of the `i`th parameter in a synthesized call that targets a + * callback of type `t`. + */ + bindingset[t, pos] + Lang::DataFlowType getCallbackParameterType(Lang::DataFlowType t, Lang::ArgumentPosition pos); - /** Gets a summary component for a return of kind `rk`. */ - SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) } + /** + * Gets the return type of kind `rk` in a synthesized call that targets a + * callback of type `t`. + */ + bindingset[t, rk] + Lang::DataFlowType getCallbackReturnType(Lang::DataFlowType t, Lang::ReturnKind rk); - /** Gets a summary component for synthetic global `sg`. */ - SummaryComponent syntheticGlobal(SyntheticGlobal sg) { - result = TSyntheticGlobalSummaryComponent(sg) - } + /** Gets the return kind corresponding to specification `"ReturnValue"`. */ + Lang::ReturnKind getStandardReturnValueKind(); - /** - * A synthetic global. This represents some form of global state, which - * summaries can read and write individually. - */ - abstract class SyntheticGlobal extends string { - bindingset[this] - SyntheticGlobal() { any() } - } - } + /** Gets the textual representation of parameter position `pos` used in MaD. */ + string encodeParameterPosition(Lang::ParameterPosition pos); + + /** Gets the textual representation of argument position `pos` used in MaD. */ + string encodeArgumentPosition(Lang::ArgumentPosition pos); /** - * A (non-empty) stack of summary components. + * Gets the textual representation of content `c` used in MaD. * - * A stack is used to represent where data is read from (input) or where it - * is written to (output). For example, an input stack `[Field f, Argument 0]` - * means that data is read from field `f` from the `0`th argument, while an - * output stack `[Field g, Return]` means that data is written to the field - * `g` of the returned object. + * `arg` will be printed in square brackets (`[]`) after the result, unless + * `arg` is the empty string. */ - class SummaryComponentStack extends TSummaryComponentStack { - /** Gets the head of this stack. */ - SummaryComponent head() { - this = TSingletonSummaryComponentStack(result) or - this = TConsSummaryComponentStack(result, _) - } + default string encodeContent(Lang::ContentSet c, string arg) { none() } - /** Gets the tail of this stack, if any. */ - SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) } - - /** Gets the length of this stack. */ - int length() { - this = TSingletonSummaryComponentStack(_) and result = 1 - or - result = 1 + this.tail().length() - } + /** + * Gets the textual representation of return kind `rk` used in MaD. + * + * `arg` will be printed in square brackets (`[]`) after the result, unless + * `arg` is the empty string. + */ + default string encodeReturn(Lang::ReturnKind rk, string arg) { none() } - /** Gets the stack obtained by dropping the first `i` elements, if any. */ - SummaryComponentStack drop(int i) { - i = 0 and result = this - or - result = this.tail().drop(i - 1) - } + /** + * Gets the textual representation of without-content `c` used in MaD. + * + * `arg` will be printed in square brackets (`[]`) after the result, unless + * `arg` is the empty string. + */ + default string encodeWithoutContent(Lang::ContentSet c, string arg) { none() } - /** Holds if this stack contains summary component `c`. */ - predicate contains(SummaryComponent c) { c = this.drop(_).head() } + /** + * Gets the textual representation of with-content `c` used in MaD. + * + * `arg` will be printed in square brackets (`[]`) after the result, unless + * `arg` is the empty string. + */ + default string encodeWithContent(Lang::ContentSet c, string arg) { none() } +} - /** Gets the bottom element of this stack. */ - SummaryComponent bottom() { - this = TSingletonSummaryComponentStack(result) or result = this.tail().bottom() - } +module Make Input> { + private import AccessPathSyntax as AccessPathSyntax + private import DataFlowLang + private import Input + private import codeql.dataflow.internal.DataFlowImplCommon::MakeImplCommon + private import codeql.util.Unit - /** Gets a textual representation of this stack used for MaD models. */ - string getMadRepresentation() { - exists(SummaryComponent head, SummaryComponentStack tail | - head = this.head() and - tail = this.tail() and - result = tail.getMadRepresentation() + "." + head.getMadRepresentation() - ) - or - exists(SummaryComponent c | - this = TSingletonSummaryComponentStack(c) and - result = c.getMadRepresentation() - ) - } + final private class SummarizedCallableBaseFinal = SummarizedCallableBase; - /** Gets a textual representation of this stack. */ - string toString() { result = this.getMadRepresentation() } - } + /** Provides classes and predicates for defining flow summaries. */ + module Public { + private import Private - /** Provides predicates for constructing stacks of summary components. */ - module SummaryComponentStack { - /** Gets a singleton stack containing `c`. */ - SummaryComponentStack singleton(SummaryComponent c) { - result = TSingletonSummaryComponentStack(c) + /** + * Gets the valid model origin values. + */ + private string getValidModelOrigin() { + result = + [ + "ai", // AI (machine learning) + "df", // Dataflow (model generator) + "tb", // Type based (model generator) + "hq", // Heuristic query + ] } /** - * Gets the stack obtained by pushing `head` onto `tail`. + * A class used to represent provenance values for MaD models. + * + * The provenance value is a string of the form `origin-verification` + * (or just `manual`), where `origin` is a value indicating the + * origin of the model, and `verification` is a value indicating, how + * the model was verified. * - * Make sure to override `RequiredSummaryComponentStack::required()` in order - * to ensure that the constructed stack exists. + * Examples could be: + * - `df-generated`: A model produced by the model generator, but not verified by a human. + * - `ai-manual`: A model produced by AI, but verified by a human. */ - SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) { - result = TConsSummaryComponentStack(head, tail) - } + class Provenance extends string { + private string verification; - /** Gets a singleton stack for an argument at position `pos`. */ - SummaryComponentStack argument(ParameterPosition pos) { - result = singleton(SummaryComponent::argument(pos)) - } + Provenance() { + exists(string origin | origin = getValidModelOrigin() | + this = origin + "-" + verification and + verification = ["manual", "generated"] + ) + or + this = verification and verification = "manual" + } - /** Gets a singleton stack representing a return of kind `rk`. */ - SummaryComponentStack return(ReturnKind rk) { result = singleton(SummaryComponent::return(rk)) } - } + /** + * Holds if this is a valid generated provenance value. + */ + predicate isGenerated() { verification = "generated" } - /** - * A class that exists for QL technical reasons only (the IPA type used - * to represent component stacks needs to be bounded). - */ - class RequiredSummaryComponentStack extends Unit { - /** - * Holds if the stack obtained by pushing `head` onto `tail` is required. - */ - abstract predicate required(SummaryComponent head, SummaryComponentStack tail); - } + /** + * Holds if this is a valid manual provenance value. + */ + predicate isManual() { verification = "manual" } + } - /** - * Gets the valid model origin values. - */ - private string getValidModelOrigin() { - result = - [ - "ai", // AI (machine learning) - "df", // Dataflow (model generator) - "tb", // Type based (model generator) - "hq", // Heuristic query - ] - } + /** A callable with a flow summary. */ + abstract class SummarizedCallable extends SummarizedCallableBaseFinal { + bindingset[this] + SummarizedCallable() { any() } - /** - * A class used to represent provenance values for MaD models. - * - * The provenance value is a string of the form `origin-verification` - * (or just `manual`), where `origin` is a value indicating the - * origin of the model, and `verification` is a value indicating, how - * the model was verified. - * - * Examples could be: - * - `df-generated`: A model produced by the model generator, but not verified by a human. - * - `ai-manual`: A model produced by AI, but verified by a human. - */ - class Provenance extends string { - private string verification; + /** + * Holds if data may flow from `input` to `output` through this callable. + * + * `preservesValue` indicates whether this is a value-preserving step or a taint-step. + */ + pragma[nomagic] + abstract predicate propagatesFlow(string input, string output, boolean preservesValue); - Provenance() { - exists(string origin | origin = getValidModelOrigin() | - this = origin + "-" + verification and - verification = ["manual", "generated"] - ) - or - this = verification and verification = "manual" - } + /** + * Holds if there exists a generated summary that applies to this callable. + */ + final predicate hasGeneratedModel() { + exists(Provenance p | p.isGenerated() and this.hasProvenance(p)) + } - /** - * Holds if this is a valid generated provenance value. - */ - predicate isGenerated() { verification = "generated" } + /** + * Holds if all the summaries that apply to this callable are auto generated and not manually created. + * That is, only apply generated models, when there are no manual models. + */ + final predicate applyGeneratedModel() { + this.hasGeneratedModel() and + not this.hasManualModel() + } - /** - * Holds if this is a valid manual provenance value. - */ - predicate isManual() { verification = "manual" } - } + /** + * Holds if there exists a manual summary that applies to this callable. + */ + final predicate hasManualModel() { + exists(Provenance p | p.isManual() and this.hasProvenance(p)) + } - /** A callable with a flow summary. */ - abstract class SummarizedCallable extends SummarizedCallableBase { - bindingset[this] - SummarizedCallable() { any() } + /** + * Holds if there exists a manual summary that applies to this callable. + * Always apply manual models if they exist. + */ + final predicate applyManualModel() { this.hasManualModel() } - /** - * Holds if data may flow from `input` to `output` through this callable. - * - * `preservesValue` indicates whether this is a value-preserving step - * or a taint-step. - * - * Input specifications are restricted to stacks that end with - * `SummaryComponent::argument(_)`, preceded by zero or more - * `SummaryComponent::return(_)` or `SummaryComponent::content(_)` components. - * - * Output specifications are restricted to stacks that end with - * `SummaryComponent::return(_)` or `SummaryComponent::argument(_)`. - * - * Output stacks ending with `SummaryComponent::return(_)` can be preceded by zero - * or more `SummaryComponent::content(_)` components. - * - * Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an - * optional `SummaryComponent::parameter(_)` component, which in turn can be preceded - * by zero or more `SummaryComponent::content(_)` components. - */ - pragma[nomagic] - predicate propagatesFlow( - SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue - ) { - none() + /** + * Holds if there exists a summary that applies to this callable + * that has provenance `provenance`. + */ + predicate hasProvenance(Provenance provenance) { provenance = "manual" } } - /** - * Holds if there exists a generated summary that applies to this callable. - */ - final predicate hasGeneratedModel() { - exists(Provenance p | p.isGenerated() and this.hasProvenance(p)) - } + final private class NeutralCallableFinal = NeutralCallable; /** - * Holds if all the summaries that apply to this callable are auto generated and not manually created. - * That is, only apply generated models, when there are no manual models. + * A callable where there is no flow via the callable. */ - final predicate applyGeneratedModel() { - this.hasGeneratedModel() and - not this.hasManualModel() + class NeutralSummaryCallable extends NeutralCallableFinal { + NeutralSummaryCallable() { this.getKind() = "summary" } } /** - * Holds if there exists a manual summary that applies to this callable. + * A callable that has a neutral model. */ - final predicate hasManualModel() { - exists(Provenance p | p.isManual() and this.hasProvenance(p)) - } + abstract class NeutralCallable extends SummarizedCallableBaseFinal { + bindingset[this] + NeutralCallable() { exists(this) } - /** - * Holds if there exists a manual summary that applies to this callable. - * Always apply manual models if they exist. - */ - final predicate applyManualModel() { this.hasManualModel() } + /** + * Holds if the neutral is auto generated. + */ + final predicate hasGeneratedModel() { + any(Provenance p | this.hasProvenance(p)).isGenerated() + } - /** - * Holds if there exists a summary that applies to this callable - * that has provenance `provenance`. - */ - predicate hasProvenance(Provenance provenance) { provenance = "manual" } - } + /** + * Holds if there exists a manual neutral that applies to this callable. + */ + final predicate hasManualModel() { any(Provenance p | this.hasProvenance(p)).isManual() } - /** - * A callable where there is no flow via the callable. - */ - class NeutralSummaryCallable extends NeutralCallable { - NeutralSummaryCallable() { this.getKind() = "summary" } + /** + * Holds if the neutral has provenance `p`. + */ + abstract predicate hasProvenance(Provenance p); + + /** + * Gets the kind of the neutral. + */ + abstract string getKind(); + } } /** - * A callable that has a neutral model. + * Provides predicates for compiling flow summaries down to atomic local steps, + * read steps, and store steps. */ - class NeutralCallable extends NeutralCallableBase { - private string kind; - private Provenance provenance; - - NeutralCallable() { neutralElement(this, kind, provenance) } + module Private { + private import Public /** - * Holds if the neutral is auto generated. + * A synthetic global. This represents some form of global state, which + * summaries can read and write individually. */ - final predicate hasGeneratedModel() { provenance.isGenerated() } + abstract class SyntheticGlobal extends string { + bindingset[this] + SyntheticGlobal() { any() } + } - /** - * Holds if there exists a manual neutral that applies to this callable. - */ - final predicate hasManualModel() { provenance.isManual() } + private newtype TSummaryComponent = + TContentSummaryComponent(ContentSet c) or + TParameterSummaryComponent(ArgumentPosition pos) or + TArgumentSummaryComponent(ParameterPosition pos) or + TReturnSummaryComponent(ReturnKind rk) or + TSyntheticGlobalSummaryComponent(SyntheticGlobal sg) or + TWithoutContentSummaryComponent(ContentSet c) or + TWithContentSummaryComponent(ContentSet c) - /** - * Holds if the neutral has provenance `p`. - */ - predicate hasProvenance(Provenance p) { p = provenance } + bindingset[name, arg] + private string encodeArg(string name, string arg) { + if arg = "" then result = name else result = name + "[" + arg + "]" + } /** - * Gets the kind of the neutral. + * A component used in a flow summary. + * + * Either a parameter or an argument at a given position, a specific + * content type, or a return kind. */ - string getKind() { result = kind } - } -} - -/** - * Provides predicates for compiling flow summaries down to atomic local steps, - * read steps, and store steps. - */ -module Private { - private import Public - import AccessPathSyntax - - newtype TSummaryComponent = - TContentSummaryComponent(ContentSet c) or - TParameterSummaryComponent(ArgumentPosition pos) or - TArgumentSummaryComponent(ParameterPosition pos) or - TReturnSummaryComponent(ReturnKind rk) or - TSyntheticGlobalSummaryComponent(SummaryComponent::SyntheticGlobal sg) or - TWithoutContentSummaryComponent(ContentSet c) or - TWithContentSummaryComponent(ContentSet c) - - private TParameterSummaryComponent callbackSelfParam() { - result = TParameterSummaryComponent(callbackSelfParameterPosition()) - } + class SummaryComponent instanceof TSummaryComponent { + /** Gets a textual representation of this component used for MaD models. */ + string getMadRepresentation() { + exists(ContentSet c, string arg | + this = TContentSummaryComponent(c) and + result = encodeArg(encodeContent(c, arg), arg) + ) + or + exists(ArgumentPosition pos | + this = TParameterSummaryComponent(pos) and + result = "Parameter[" + encodeArgumentPosition(pos) + "]" + ) + or + exists(ParameterPosition pos | + this = TArgumentSummaryComponent(pos) and + result = "Argument[" + encodeParameterPosition(pos) + "]" + ) + or + exists(string synthetic | + this = TSyntheticGlobalSummaryComponent(synthetic) and + result = "SyntheticGlobal[" + synthetic + "]" + ) + or + exists(ReturnKind rk | this = TReturnSummaryComponent(rk) | + rk = getStandardReturnValueKind() and result = "ReturnValue" + or + exists(string arg | result = encodeArg(encodeReturn(rk, arg), arg)) + ) + or + exists(ContentSet c, string arg | + this = TWithoutContentSummaryComponent(c) and + result = encodeArg(encodeWithoutContent(c, arg), arg) + ) + or + exists(ContentSet c, string arg | + this = TWithContentSummaryComponent(c) and + result = encodeArg(encodeWithContent(c, arg), arg) + ) + } - newtype TSummaryComponentStack = - TSingletonSummaryComponentStack(SummaryComponent c) or - TConsSummaryComponentStack(SummaryComponent head, SummaryComponentStack tail) { - any(RequiredSummaryComponentStack x).required(head, tail) - or - any(RequiredSummaryComponentStack x).required(TParameterSummaryComponent(_), tail) and - head = callbackSelfParam() - or - derivedFluentFlowPush(_, _, _, head, tail, _) + /** Gets a textual representation of this summary component. */ + string toString() { result = this.getMadRepresentation() } } - pragma[nomagic] - private predicate summary( - SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output, - boolean preservesValue - ) { - c.propagatesFlow(input, output, preservesValue) - or - // observe side effects of callbacks on input arguments - c.propagatesFlow(output, input, preservesValue) and - preservesValue = true and - isCallbackParameter(input) and - isContentOfArgument(output, _) - or - // flow from the receiver of a callback into the instance-parameter - exists(SummaryComponentStack s, SummaryComponentStack callbackRef | - c.propagatesFlow(s, _, _) or c.propagatesFlow(_, s, _) - | - callbackRef = s.drop(_) and - (isCallbackParameter(callbackRef) or callbackRef.head() = TReturnSummaryComponent(_)) and - input = callbackRef.tail() and - output = TConsSummaryComponentStack(callbackSelfParam(), input) and - preservesValue = true - ) - or - exists(SummaryComponentStack arg, SummaryComponentStack return | - derivedFluentFlow(c, input, arg, return, preservesValue) - | - arg.length() = 1 and - output = return - or - exists(SummaryComponent head, SummaryComponentStack tail | - derivedFluentFlowPush(c, input, arg, head, tail, 0) and - output = SummaryComponentStack::push(head, tail) - ) - ) - or - // Chain together summaries where values get passed into callbacks along the way - exists(SummaryComponentStack mid, boolean preservesValue1, boolean preservesValue2 | - c.propagatesFlow(input, mid, preservesValue1) and - c.propagatesFlow(mid, output, preservesValue2) and - mid.drop(mid.length() - 2) = - SummaryComponentStack::push(TParameterSummaryComponent(_), - SummaryComponentStack::singleton(TArgumentSummaryComponent(_))) and - preservesValue = preservesValue1.booleanAnd(preservesValue2) - ) - } + /** Provides predicates for constructing summary components. */ + module SummaryComponent { + /** Gets a summary component for content `c`. */ + SummaryComponent content(ContentSet c) { result = TContentSummaryComponent(c) } - /** - * Holds if `c` has a flow summary from `input` to `arg`, where `arg` - * writes to (contents of) arguments at position `pos`, and `c` has a - * value-preserving flow summary from the arguments at position `pos` - * to a return value (`return`). - * - * In such a case, we derive flow from `input` to (contents of) the return - * value. - * - * As an example, this simplifies modeling of fluent methods: - * for `StringBuilder.append(x)` with a specified value flow from qualifier to - * return value and taint flow from argument 0 to the qualifier, then this - * allows us to infer taint flow from argument 0 to the return value. - */ - pragma[nomagic] - private predicate derivedFluentFlow( - SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg, - SummaryComponentStack return, boolean preservesValue - ) { - exists(ParameterPosition pos | - summary(c, input, arg, preservesValue) and - isContentOfArgument(arg, pos) and - summary(c, SummaryComponentStack::argument(pos), return, true) and - return.bottom() = TReturnSummaryComponent(_) - ) - } + /** Gets a summary component where data is not allowed to be stored in `c`. */ + SummaryComponent withoutContent(ContentSet c) { result = TWithoutContentSummaryComponent(c) } - pragma[nomagic] - private predicate derivedFluentFlowPush( - SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg, - SummaryComponent head, SummaryComponentStack tail, int i - ) { - derivedFluentFlow(c, input, arg, tail, _) and - head = arg.drop(i).head() and - i = arg.length() - 2 - or - exists(SummaryComponent head0, SummaryComponentStack tail0 | - derivedFluentFlowPush(c, input, arg, head0, tail0, i + 1) and - head = arg.drop(i).head() and - tail = SummaryComponentStack::push(head0, tail0) - ) - } + /** Gets a summary component where data must be stored in `c`. */ + SummaryComponent withContent(ContentSet c) { result = TWithContentSummaryComponent(c) } - private predicate isCallbackParameter(SummaryComponentStack s) { - s.head() = TParameterSummaryComponent(_) and exists(s.tail()) - } + /** Gets a summary component for a parameter at position `pos`. */ + SummaryComponent parameter(ArgumentPosition pos) { result = TParameterSummaryComponent(pos) } - private predicate isContentOfArgument(SummaryComponentStack s, ParameterPosition pos) { - s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail(), pos) - or - s = SummaryComponentStack::argument(pos) - } + /** Gets a summary component for an argument at position `pos`. */ + SummaryComponent argument(ParameterPosition pos) { result = TArgumentSummaryComponent(pos) } - private predicate outputState(SummarizedCallable c, SummaryComponentStack s) { - summary(c, _, s, _) - or - exists(SummaryComponentStack out | - outputState(c, out) and - out.head() = TContentSummaryComponent(_) and - s = out.tail() - ) - or - // Add the argument node corresponding to the requested post-update node - inputState(c, s) and isCallbackParameter(s) - } + /** Gets a summary component for a return of kind `rk`. */ + SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) } - private predicate inputState(SummarizedCallable c, SummaryComponentStack s) { - summary(c, s, _, _) - or - exists(SummaryComponentStack inp | inputState(c, inp) and s = inp.tail()) - or - exists(SummaryComponentStack out | - outputState(c, out) and - out.head() = TParameterSummaryComponent(_) and - s = out.tail() - ) - or - // Add the post-update node corresponding to the requested argument node - outputState(c, s) and isCallbackParameter(s) - or - // Add the parameter node for parameter side-effects - outputState(c, s) and s = SummaryComponentStack::argument(_) - } + /** Gets a summary component for synthetic global `sg`. */ + SummaryComponent syntheticGlobal(SyntheticGlobal sg) { + result = TSyntheticGlobalSummaryComponent(sg) + } + } - private newtype TSummaryNodeState = - TSummaryNodeInputState(SummaryComponentStack s) { inputState(_, s) } or - TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) } + private predicate summaryElement( + SummarizedCallable c, string input, string output, boolean preservesValue, string provenance + ) { + c.propagatesFlow(input, output, preservesValue) and + c.hasProvenance(provenance) + } - /** - * A state used to break up (complex) flow summaries into atomic flow steps. - * For a flow summary - * - * ```ql - * propagatesFlow( - * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue - * ) - * ``` - * - * the following states are used: - * - * - `TSummaryNodeInputState(SummaryComponentStack s)`: - * this state represents that the components in `s` _have been read_ from the - * input. - * - `TSummaryNodeOutputState(SummaryComponentStack s)`: - * this state represents that the components in `s` _remain to be written_ to - * the output. - */ - private class SummaryNodeState extends TSummaryNodeState { - /** Holds if this state is a valid input state for `c`. */ - pragma[nomagic] - predicate isInputState(SummarizedCallable c, SummaryComponentStack s) { - this = TSummaryNodeInputState(s) and - inputState(c, s) + private predicate summarySpec(string spec) { + summaryElement(_, spec, _, _, _) or + summaryElement(_, _, spec, _, _) } - /** Holds if this state is a valid output state for `c`. */ - pragma[nomagic] - predicate isOutputState(SummarizedCallable c, SummaryComponentStack s) { - this = TSummaryNodeOutputState(s) and - outputState(c, s) + import AccessPathSyntax::AccessPath + + /** Holds if specification component `token` parses as parameter `pos`. */ + predicate parseParam(AccessPathToken token, ArgumentPosition pos) { + token.getName() = "Parameter" and + token.getAnArgument() = encodeArgumentPosition(pos) } - /** Gets a textual representation of this state. */ - string toString() { - exists(SummaryComponentStack s | - this = TSummaryNodeInputState(s) and - result = "read: " + s - ) - or - exists(SummaryComponentStack s | - this = TSummaryNodeOutputState(s) and - result = "to write: " + s - ) + /** Holds if specification component `token` parses as argument `pos`. */ + predicate parseArg(AccessPathToken token, ParameterPosition pos) { + token.getName() = "Argument" and + token.getAnArgument() = encodeParameterPosition(pos) } - } - private newtype TSummaryNode = - TSummaryInternalNode(SummarizedCallable c, SummaryNodeState state) { - summaryNodeRange(c, state) - } or - TSummaryParameterNode(SummarizedCallable c, ParameterPosition pos) { - summaryParameterNodeRange(c, pos) + /** Holds if specification component `token` parses as synthetic global `sg`. */ + predicate parseSynthGlobal(AccessPathToken token, string sg) { + token.getName() = "SyntheticGlobal" and + sg = token.getAnArgument() } - abstract class SummaryNode extends TSummaryNode { - abstract string toString(); + private class SyntheticGlobalFromAccessPath extends SyntheticGlobal { + SyntheticGlobalFromAccessPath() { parseSynthGlobal(_, this) } + } - abstract SummarizedCallable getSummarizedCallable(); - } + private TParameterSummaryComponent callbackSelfParam() { + result = TParameterSummaryComponent(callbackSelfParameterPosition()) + } - private class SummaryInternalNode extends SummaryNode, TSummaryInternalNode { - private SummarizedCallable c; - private SummaryNodeState state; + newtype TSummaryComponentStack = + TSingletonSummaryComponentStack(SummaryComponent c) or + TConsSummaryComponentStack(SummaryComponent head, SummaryComponentStack tail) { + any(RequiredSummaryComponentStack x).required(head, tail) + or + any(RequiredSummaryComponentStack x).required(TParameterSummaryComponent(_), tail) and + head = callbackSelfParam() + or + derivedFluentFlowPush(_, _, _, head, tail, _) + } - SummaryInternalNode() { this = TSummaryInternalNode(c, state) } + /** + * A (non-empty) stack of summary components. + * + * A stack is used to represent where data is read from (input) or where it + * is written to (output). For example, an input stack `[Field f, Argument 0]` + * means that data is read from field `f` from the `0`th argument, while an + * output stack `[Field g, Return]` means that data is written to the field + * `g` of the returned object. + */ + class SummaryComponentStack extends TSummaryComponentStack { + /** Gets the head of this stack. */ + SummaryComponent head() { + this = TSingletonSummaryComponentStack(result) or + this = TConsSummaryComponentStack(result, _) + } - override string toString() { result = "[summary] " + state + " in " + c } + /** Gets the tail of this stack, if any. */ + SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) } - override SummarizedCallable getSummarizedCallable() { result = c } - } + /** Gets the length of this stack. */ + int length() { + this = TSingletonSummaryComponentStack(_) and result = 1 + or + result = 1 + this.tail().length() + } - private class SummaryParamNode extends SummaryNode, TSummaryParameterNode { - private SummarizedCallable c; - private ParameterPosition pos; + /** Gets the stack obtained by dropping the first `i` elements, if any. */ + SummaryComponentStack drop(int i) { + i = 0 and result = this + or + result = this.tail().drop(i - 1) + } - SummaryParamNode() { this = TSummaryParameterNode(c, pos) } + /** Holds if this stack contains summary component `c`. */ + predicate contains(SummaryComponent c) { c = this.drop(_).head() } - override string toString() { result = "[summary param] " + pos + " in " + c } + /** Gets the bottom element of this stack. */ + SummaryComponent bottom() { + this = TSingletonSummaryComponentStack(result) or result = this.tail().bottom() + } - override SummarizedCallable getSummarizedCallable() { result = c } - } + /** Gets a textual representation of this stack used for MaD models. */ + string getMadRepresentation() { + exists(SummaryComponent head, SummaryComponentStack tail | + head = this.head() and + tail = this.tail() and + result = tail.getMadRepresentation() + "." + head.getMadRepresentation() + ) + or + exists(SummaryComponent c | + this = TSingletonSummaryComponentStack(c) and + result = c.getMadRepresentation() + ) + } - /** - * Holds if `state` represents having read from a parameter at position - * `pos` in `c`. In this case we are not synthesizing a data-flow node, - * but instead assume that a relevant parameter node already exists. - */ - private predicate parameterReadState( - SummarizedCallable c, SummaryNodeState state, ParameterPosition pos - ) { - state.isInputState(c, SummaryComponentStack::argument(pos)) - } + /** Gets a textual representation of this stack. */ + string toString() { result = this.getMadRepresentation() } + } - /** - * Holds if a synthesized summary node is needed for the state `state` in summarized - * callable `c`. - */ - private predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) { - state.isInputState(c, _) and - not parameterReadState(c, state, _) - or - state.isOutputState(c, _) - } + /** Provides predicates for constructing stacks of summary components. */ + module SummaryComponentStack { + /** Gets a singleton stack containing `c`. */ + SummaryComponentStack singleton(SummaryComponent c) { + result = TSingletonSummaryComponentStack(c) + } - pragma[noinline] - private SummaryNode summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) { - exists(SummaryNodeState state | state.isInputState(c, s) | - result = TSummaryInternalNode(c, state) - or - exists(ParameterPosition pos | - parameterReadState(c, state, pos) and - result = TSummaryParameterNode(c, pos) - ) - ) - } + /** + * Gets the stack obtained by pushing `head` onto `tail`. + * + * Make sure to override `RequiredSummaryComponentStack::required()` in order + * to ensure that the constructed stack exists. + */ + SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) { + result = TConsSummaryComponentStack(head, tail) + } - pragma[noinline] - private SummaryNode summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) { - exists(SummaryNodeState state | - state.isOutputState(c, s) and - result = TSummaryInternalNode(c, state) - ) - } + /** Gets a singleton stack for an argument at position `pos`. */ + SummaryComponentStack argument(ParameterPosition pos) { + result = singleton(SummaryComponent::argument(pos)) + } - /** - * Holds if a write targets `post`, which is a post-update node for a - * parameter at position `pos` in `c`. - */ - private predicate isParameterPostUpdate( - SummaryNode post, SummarizedCallable c, ParameterPosition pos - ) { - post = summaryNodeOutputState(c, SummaryComponentStack::argument(pos)) - } + /** Gets a singleton stack representing a return of kind `rk`. */ + SummaryComponentStack return(ReturnKind rk) { + result = singleton(SummaryComponent::return(rk)) + } + } - /** Holds if a parameter node at position `pos` is required for `c`. */ - private predicate summaryParameterNodeRange(SummarizedCallable c, ParameterPosition pos) { - parameterReadState(c, _, pos) - or - // Same as `isParameterPostUpdate(_, c, pos)`, but can be used in a negative context - any(SummaryNodeState state).isOutputState(c, SummaryComponentStack::argument(pos)) - } + /** + * A class that exists for QL technical reasons only (the IPA type used + * to represent component stacks needs to be bounded). + */ + class RequiredSummaryComponentStack extends Unit { + /** + * Holds if the stack obtained by pushing `head` onto `tail` is required. + */ + abstract predicate required(SummaryComponent head, SummaryComponentStack tail); + } - private predicate callbackOutput( - SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ReturnKind rk - ) { - any(SummaryNodeState state).isInputState(c, s) and - s.head() = TReturnSummaryComponent(rk) and - receiver = summaryNodeInputState(c, s.tail()) - } + /** A callable with a flow summary. */ + abstract class SummarizedCallableImpl extends SummarizedCallableBaseFinal { + bindingset[this] + SummarizedCallableImpl() { any() } - private predicate callbackInput( - SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ArgumentPosition pos - ) { - any(SummaryNodeState state).isOutputState(c, s) and - s.head() = TParameterSummaryComponent(pos) and - receiver = summaryNodeInputState(c, s.tail()) - } + /** + * Holds if data may flow from `input` to `output` through this callable. + * + * `preservesValue` indicates whether this is a value-preserving step + * or a taint-step. + * + * Input specifications are restricted to stacks that end with + * `SummaryComponent::argument(_)`, preceded by zero or more + * `SummaryComponent::return(_)` or `SummaryComponent::content(_)` components. + * + * Output specifications are restricted to stacks that end with + * `SummaryComponent::return(_)` or `SummaryComponent::argument(_)`. + * + * Output stacks ending with `SummaryComponent::return(_)` can be preceded by zero + * or more `SummaryComponent::content(_)` components. + * + * Output stacks ending with `SummaryComponent::argument(_)` can be preceded by an + * optional `SummaryComponent::parameter(_)` component, which in turn can be preceded + * by zero or more `SummaryComponent::content(_)` components. + */ + pragma[nomagic] + abstract predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ); - /** Holds if a call targeting `receiver` should be synthesized inside `c`. */ - predicate summaryCallbackRange(SummarizedCallable c, SummaryNode receiver) { - callbackOutput(c, _, receiver, _) - or - callbackInput(c, _, receiver, _) - } + /** + * Holds if there exists a summary that applies to this callable + * that has provenance `provenance`. + */ + abstract predicate hasProvenance(Provenance provenance); + } - /** - * Gets the type of synthesized summary node `n`. - * - * The type is computed based on the language-specific predicates - * `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and - * `getCallbackReturnType()`. - */ - DataFlowType summaryNodeType(SummaryNode n) { - exists(SummaryNode pre | - summaryPostUpdateNode(n, pre) and - result = summaryNodeType(pre) - ) - or - exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | head = s.head() | - n = summaryNodeInputState(c, s) and - ( - exists(ContentSet cont | result = getContentType(cont) | - head = TContentSummaryComponent(cont) or - head = TWithContentSummaryComponent(cont) - ) - or - head = TWithoutContentSummaryComponent(_) and - result = summaryNodeType(summaryNodeInputState(c, s.tail())) - or - exists(ReturnKind rk | - head = TReturnSummaryComponent(rk) and - result = - getCallbackReturnType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c), - s.tail())), rk) - ) - or - exists(SummaryComponent::SyntheticGlobal sg | - head = TSyntheticGlobalSummaryComponent(sg) and - result = getSyntheticGlobalType(sg) - ) - or - exists(ParameterPosition pos | - head = TArgumentSummaryComponent(pos) and - result = getParameterType(c, pos) - ) + pragma[nomagic] + private predicate summary( + SummarizedCallableImpl c, SummaryComponentStack input, SummaryComponentStack output, + boolean preservesValue + ) { + c.propagatesFlow(input, output, preservesValue) + or + // observe side effects of callbacks on input arguments + c.propagatesFlow(output, input, preservesValue) and + preservesValue = true and + isCallbackParameter(input) and + isContentOfArgument(output, _) + or + // flow from the receiver of a callback into the instance-parameter + exists(SummaryComponentStack s, SummaryComponentStack callbackRef | + c.propagatesFlow(s, _, _) or c.propagatesFlow(_, s, _) + | + callbackRef = s.drop(_) and + (isCallbackParameter(callbackRef) or callbackRef.head() = TReturnSummaryComponent(_)) and + input = callbackRef.tail() and + output = TConsSummaryComponentStack(callbackSelfParam(), input) and + preservesValue = true ) or - n = summaryNodeOutputState(c, s) and - ( - exists(ContentSet cont | - head = TContentSummaryComponent(cont) and result = getContentType(cont) - ) - or - s.length() = 1 and - exists(ReturnKind rk | - head = TReturnSummaryComponent(rk) and - result = getReturnType(c, rk) - ) - or - exists(ArgumentPosition pos | head = TParameterSummaryComponent(pos) | - result = - getCallbackParameterType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c), - s.tail())), pos) - ) + exists(SummaryComponentStack arg, SummaryComponentStack return | + derivedFluentFlow(c, input, arg, return, preservesValue) + | + arg.length() = 1 and + output = return or - exists(SummaryComponent::SyntheticGlobal sg | - head = TSyntheticGlobalSummaryComponent(sg) and - result = getSyntheticGlobalType(sg) + exists(SummaryComponent head, SummaryComponentStack tail | + derivedFluentFlowPush(c, input, arg, head, tail, 0) and + output = SummaryComponentStack::push(head, tail) ) ) - ) - } - - /** Holds if summary node `p` is a parameter with position `pos`. */ - predicate summaryParameterNode(SummaryNode p, ParameterPosition pos) { - p = TSummaryParameterNode(_, pos) - } - - /** Holds if summary node `out` contains output of kind `rk` from call `c`. */ - predicate summaryOutNode(DataFlowCall c, SummaryNode out, ReturnKind rk) { - exists(SummarizedCallable callable, SummaryComponentStack s, SummaryNode receiver | - callbackOutput(callable, s, receiver, rk) and - out = summaryNodeInputState(callable, s) and - c = summaryDataFlowCall(receiver) - ) - } - - /** Holds if summary node `arg` is at position `pos` in the call `c`. */ - predicate summaryArgumentNode(DataFlowCall c, SummaryNode arg, ArgumentPosition pos) { - exists(SummarizedCallable callable, SummaryComponentStack s, SummaryNode receiver | - callbackInput(callable, s, receiver, pos) and - arg = summaryNodeOutputState(callable, s) and - c = summaryDataFlowCall(receiver) - ) - } - - /** Holds if summary node `post` is a post-update node with pre-update node `pre`. */ - predicate summaryPostUpdateNode(SummaryNode post, SummaryNode pre) { - exists(SummarizedCallable c, ParameterPosition pos | - isParameterPostUpdate(post, c, pos) and - pre = TSummaryParameterNode(c, pos) - ) - or - exists(SummarizedCallable callable, SummaryComponentStack s | - callbackInput(callable, s, _, _) and - pre = summaryNodeOutputState(callable, s) and - post = summaryNodeInputState(callable, s) - ) - } - - /** Holds if summary node `ret` is a return node of kind `rk`. */ - predicate summaryReturnNode(SummaryNode ret, ReturnKind rk) { - exists(SummaryComponentStack s | - ret = summaryNodeOutputState(_, s) and - s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk)) - ) - } - - /** - * Holds if flow is allowed to pass from parameter `p`, to a return - * node, and back out to `p`. - */ - predicate summaryAllowParameterReturnInSelf(ParamNode p) { - exists(SummarizedCallable c, ParameterPosition ppos | - p.isParameterOf(inject(c), pragma[only_bind_into](ppos)) - | - exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents | - summary(c, inputContents, outputContents, _) and - inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) and - outputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) + or + // Chain together summaries where values get passed into callbacks along the way + exists(SummaryComponentStack mid, boolean preservesValue1, boolean preservesValue2 | + c.propagatesFlow(input, mid, preservesValue1) and + c.propagatesFlow(mid, output, preservesValue2) and + mid.drop(mid.length() - 2) = + SummaryComponentStack::push(TParameterSummaryComponent(_), + SummaryComponentStack::singleton(TArgumentSummaryComponent(_))) and + preservesValue = preservesValue1.booleanAnd(preservesValue2) ) - ) - } + } - /** Provides a compilation of flow summaries to atomic data-flow steps. */ - module Steps { /** - * Holds if there is a local step from `pred` to `succ`, which is synthesized - * from a flow summary. + * Holds if `c` has a flow summary from `input` to `arg`, where `arg` + * writes to (contents of) arguments at position `pos`, and `c` has a + * value-preserving flow summary from the arguments at position `pos` + * to a return value (`return`). + * + * In such a case, we derive flow from `input` to (contents of) the return + * value. + * + * As an example, this simplifies modeling of fluent methods: + * for `StringBuilder.append(x)` with a specified value flow from qualifier to + * return value and taint flow from argument 0 to the qualifier, then this + * allows us to infer taint flow from argument 0 to the return value. */ - predicate summaryLocalStep(SummaryNode pred, SummaryNode succ, boolean preservesValue) { - exists( - SummarizedCallable c, SummaryComponentStack inputContents, - SummaryComponentStack outputContents - | - summary(c, inputContents, outputContents, preservesValue) and - pred = summaryNodeInputState(c, inputContents) and - succ = summaryNodeOutputState(c, outputContents) - | - preservesValue = true - or - preservesValue = false and not summary(c, inputContents, outputContents, true) + pragma[nomagic] + private predicate derivedFluentFlow( + SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg, + SummaryComponentStack return, boolean preservesValue + ) { + exists(ParameterPosition pos | + summary(c, input, arg, preservesValue) and + isContentOfArgument(arg, pos) and + summary(c, SummaryComponentStack::argument(pos), return, true) and + return.bottom() = TReturnSummaryComponent(_) ) + } + + pragma[nomagic] + private predicate derivedFluentFlowPush( + SummarizedCallable c, SummaryComponentStack input, SummaryComponentStack arg, + SummaryComponent head, SummaryComponentStack tail, int i + ) { + derivedFluentFlow(c, input, arg, tail, _) and + head = arg.drop(i).head() and + i = arg.length() - 2 or - exists(SummarizedCallable c, SummaryComponentStack s | - pred = summaryNodeInputState(c, s.tail()) and - succ = summaryNodeInputState(c, s) and - s.head() = [SummaryComponent::withContent(_), SummaryComponent::withoutContent(_)] and - preservesValue = true + exists(SummaryComponent head0, SummaryComponentStack tail0 | + derivedFluentFlowPush(c, input, arg, head0, tail0, i + 1) and + head = arg.drop(i).head() and + tail = SummaryComponentStack::push(head0, tail0) ) } - /** - * Holds if there is a read step of content `c` from `pred` to `succ`, which - * is synthesized from a flow summary. - */ - predicate summaryReadStep(SummaryNode pred, ContentSet c, SummaryNode succ) { - exists(SummarizedCallable sc, SummaryComponentStack s | - pred = summaryNodeInputState(sc, s.tail()) and - succ = summaryNodeInputState(sc, s) and - SummaryComponent::content(c) = s.head() - ) + private predicate isCallbackParameter(SummaryComponentStack s) { + s.head() = TParameterSummaryComponent(_) and exists(s.tail()) } - /** - * Holds if there is a store step of content `c` from `pred` to `succ`, which - * is synthesized from a flow summary. - */ - predicate summaryStoreStep(SummaryNode pred, ContentSet c, SummaryNode succ) { - exists(SummarizedCallable sc, SummaryComponentStack s | - pred = summaryNodeOutputState(sc, s) and - succ = summaryNodeOutputState(sc, s.tail()) and - SummaryComponent::content(c) = s.head() + private predicate isContentOfArgument(SummaryComponentStack s, ParameterPosition pos) { + s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail(), pos) + or + s = SummaryComponentStack::argument(pos) + } + + private predicate outputState(SummarizedCallable c, SummaryComponentStack s) { + summary(c, _, s, _) + or + exists(SummaryComponentStack out | + outputState(c, out) and + out.head() = TContentSummaryComponent(_) and + s = out.tail() ) + or + // Add the argument node corresponding to the requested post-update node + inputState(c, s) and isCallbackParameter(s) } - /** - * Holds if there is a jump step from `pred` to `succ`, which is synthesized - * from a flow summary. - */ - predicate summaryJumpStep(SummaryNode pred, SummaryNode succ) { - exists(SummaryComponentStack s | - s = SummaryComponentStack::singleton(SummaryComponent::syntheticGlobal(_)) and - pred = summaryNodeOutputState(_, s) and - succ = summaryNodeInputState(_, s) + private predicate inputState(SummarizedCallable c, SummaryComponentStack s) { + summary(c, s, _, _) + or + exists(SummaryComponentStack inp | inputState(c, inp) and s = inp.tail()) + or + exists(SummaryComponentStack out | + outputState(c, out) and + out.head() = TParameterSummaryComponent(_) and + s = out.tail() ) + or + // Add the post-update node corresponding to the requested argument node + outputState(c, s) and isCallbackParameter(s) + or + // Add the parameter node for parameter side-effects + outputState(c, s) and s = SummaryComponentStack::argument(_) } + private newtype TSummaryNodeState = + TSummaryNodeInputState(SummaryComponentStack s) { inputState(_, s) } or + TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) } + /** - * Holds if values stored inside content `c` are cleared at `n`. `n` is a - * synthesized summary node, so in order for values to be cleared at calls - * to the relevant method, it is important that flow does not pass over - * the argument, either via use-use flow or def-use flow. + * A state used to break up (complex) flow summaries into atomic flow steps. + * For a flow summary * - * Example: - * - * ``` - * a.b = taint; - * a.clearB(); // assume we have a flow summary for `clearB` that clears `b` on the qualifier - * sink(a.b); + * ```ql + * propagatesFlow( + * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + * ) * ``` * - * In the above, flow should not pass from `a` on the first line (or the second - * line) to `a` on the third line. Instead, there will be synthesized flow from - * `a` on line 2 to the post-update node for `a` on that line (via an intermediate - * node where field `b` is cleared). + * the following states are used: + * + * - `TSummaryNodeInputState(SummaryComponentStack s)`: + * this state represents that the components in `s` _have been read_ from the + * input. + * - `TSummaryNodeOutputState(SummaryComponentStack s)`: + * this state represents that the components in `s` _remain to be written_ to + * the output. */ - predicate summaryClearsContent(SummaryNode n, ContentSet c) { - exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack | - n = TSummaryInternalNode(sc, state) and - state.isInputState(sc, stack) and - stack.head() = SummaryComponent::withoutContent(c) - ) - } + private class SummaryNodeState extends TSummaryNodeState { + /** Holds if this state is a valid input state for `c`. */ + pragma[nomagic] + predicate isInputState(SummarizedCallable c, SummaryComponentStack s) { + this = TSummaryNodeInputState(s) and + inputState(c, s) + } - /** - * Holds if the value that is being tracked is expected to be stored inside - * content `c` at `n`. - */ - predicate summaryExpectsContent(SummaryNode n, ContentSet c) { - exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack | - n = TSummaryInternalNode(sc, state) and - state.isInputState(sc, stack) and - stack.head() = SummaryComponent::withContent(c) - ) - } + /** Holds if this state is a valid output state for `c`. */ + pragma[nomagic] + predicate isOutputState(SummarizedCallable c, SummaryComponentStack s) { + this = TSummaryNodeOutputState(s) and + outputState(c, s) + } - pragma[noinline] - private predicate viableParam( - DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, SummaryParamNode p - ) { - exists(DataFlowCallable c | - c = inject(sc) and - p = TSummaryParameterNode(sc, ppos) and - c = viableCallable(call) - ) + /** Gets a textual representation of this state. */ + string toString() { + exists(SummaryComponentStack s | + this = TSummaryNodeInputState(s) and + result = "read: " + s + ) + or + exists(SummaryComponentStack s | + this = TSummaryNodeOutputState(s) and + result = "to write: " + s + ) + } } - pragma[nomagic] - private SummaryParamNode summaryArgParam(DataFlowCall call, ArgNode arg, SummarizedCallable sc) { - exists(ParameterPosition ppos | - argumentPositionMatch(call, arg, ppos) and - viableParam(call, sc, ppos, result) - ) - } + private newtype TSummaryNode = + TSummaryInternalNode(SummarizedCallable c, SummaryNodeState state) { + summaryNodeRange(c, state) + } or + TSummaryParameterNode(SummarizedCallable c, ParameterPosition pos) { + summaryParameterNodeRange(c, pos) + } - /** - * Holds if `p` can reach `n` in a summarized callable, using only value-preserving - * local steps. `clearsOrExpects` records whether any node on the path from `p` to - * `n` either clears or expects contents. - */ - private predicate paramReachesLocal(SummaryParamNode p, SummaryNode n, boolean clearsOrExpects) { - viableParam(_, _, _, p) and - n = p and - clearsOrExpects = false - or - exists(SummaryNode mid, boolean clearsOrExpectsMid | - paramReachesLocal(p, mid, clearsOrExpectsMid) and - summaryLocalStep(mid, n, true) and - if - summaryClearsContent(n, _) or - summaryExpectsContent(n, _) - then clearsOrExpects = true - else clearsOrExpects = clearsOrExpectsMid - ) - } + abstract class SummaryNode extends TSummaryNode { + abstract string toString(); - /** - * Holds if use-use flow starting from `arg` should be prohibited. - * - * This is the case when `arg` is the argument of a call that targets a - * flow summary where the corresponding parameter either clears contents - * or expects contents. - */ - pragma[nomagic] - predicate prohibitsUseUseFlow(ArgNode arg, SummarizedCallable sc) { - exists(SummaryParamNode p, ParameterPosition ppos, SummaryNode ret | - paramReachesLocal(p, ret, true) and - p = summaryArgParam(_, arg, sc) and - p = TSummaryParameterNode(_, pragma[only_bind_into](ppos)) and - isParameterPostUpdate(ret, _, pragma[only_bind_into](ppos)) - ) + abstract SummarizedCallable getSummarizedCallable(); } - pragma[nomagic] - private predicate summaryReturnNodeExt(SummaryNode ret, ReturnKindExt rk) { - summaryReturnNode(ret, rk.(ValueReturnKind).getKind()) - or - exists(SummaryParamNode p, SummaryNode pre, ParameterPosition pos | - paramReachesLocal(p, pre, _) and - summaryPostUpdateNode(ret, pre) and - p = TSummaryParameterNode(_, pos) and - rk.(ParamUpdateReturnKind).getPosition() = pos - ) + private class SummaryInternalNode extends SummaryNode, TSummaryInternalNode { + private SummarizedCallable c; + private SummaryNodeState state; + + SummaryInternalNode() { this = TSummaryInternalNode(c, state) } + + override string toString() { result = "[summary] " + state + " in " + c } + + override SummarizedCallable getSummarizedCallable() { result = c } } - bindingset[ret] - private SummaryParamNode summaryArgParamRetOut( - ArgNode arg, SummaryNode ret, OutNodeExt out, SummarizedCallable sc - ) { - exists(DataFlowCall call, ReturnKindExt rk | - result = summaryArgParam(call, arg, sc) and - summaryReturnNodeExt(ret, pragma[only_bind_into](rk)) and - out = pragma[only_bind_into](rk).getAnOutNode(call) - ) + private class SummaryParamNode extends SummaryNode, TSummaryParameterNode { + private SummarizedCallable c; + private ParameterPosition pos; + + SummaryParamNode() { this = TSummaryParameterNode(c, pos) } + + override string toString() { result = "[summary param] " + pos + " in " + c } + + override SummarizedCallable getSummarizedCallable() { result = c } } /** - * Holds if `arg` flows to `out` using a simple value-preserving flow - * summary, that is, a flow summary without reads and stores. - * - * NOTE: This step should not be used in global data-flow/taint-tracking, but may - * be useful to include in the exposed local data-flow/taint-tracking relations. + * Holds if `state` represents having read from a parameter at position + * `pos` in `c`. In this case we are not synthesizing a data-flow node, + * but instead assume that a relevant parameter node already exists. */ - predicate summaryThroughStepValue(ArgNode arg, Node out, SummarizedCallable sc) { - exists(ReturnKind rk, SummaryNode ret, DataFlowCall call | - summaryLocalStep(summaryArgParam(call, arg, sc), ret, true) and - summaryReturnNode(ret, pragma[only_bind_into](rk)) and - out = getAnOutNode(call, pragma[only_bind_into](rk)) - ) + private predicate parameterReadState( + SummarizedCallable c, SummaryNodeState state, ParameterPosition pos + ) { + state.isInputState(c, SummaryComponentStack::argument(pos)) } /** - * Holds if `arg` flows to `out` using a simple flow summary involving taint - * step, that is, a flow summary without reads and stores. - * - * NOTE: This step should not be used in global data-flow/taint-tracking, but may - * be useful to include in the exposed local data-flow/taint-tracking relations. + * Holds if a synthesized summary node is needed for the state `state` in summarized + * callable `c`. */ - predicate summaryThroughStepTaint(ArgNode arg, Node out, SummarizedCallable sc) { - exists(SummaryNode ret | - summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), ret, false) + private predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) { + state.isInputState(c, _) and + not parameterReadState(c, state, _) + or + state.isOutputState(c, _) + } + + pragma[noinline] + private SummaryNode summaryNodeInputState(SummarizedCallable c, SummaryComponentStack s) { + exists(SummaryNodeState state | state.isInputState(c, s) | + result = TSummaryInternalNode(c, state) + or + exists(ParameterPosition pos | + parameterReadState(c, state, pos) and + result = TSummaryParameterNode(c, pos) + ) ) } - /** - * Holds if there is a read(+taint) of `c` from `arg` to `out` using a - * flow summary. - * - * NOTE: This step should not be used in global data-flow/taint-tracking, but may - * be useful to include in the exposed local data-flow/taint-tracking relations. - */ - predicate summaryGetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) { - exists(SummaryNode mid, SummaryNode ret | - summaryReadStep(summaryArgParamRetOut(arg, ret, out, sc), c, mid) and - summaryLocalStep(mid, ret, _) + pragma[noinline] + private SummaryNode summaryNodeOutputState(SummarizedCallable c, SummaryComponentStack s) { + exists(SummaryNodeState state | + state.isOutputState(c, s) and + result = TSummaryInternalNode(c, state) ) } /** - * Holds if there is a (taint+)store of `arg` into content `c` of `out` using a - * flow summary. - * - * NOTE: This step should not be used in global data-flow/taint-tracking, but may - * be useful to include in the exposed local data-flow/taint-tracking relations. + * Holds if a write targets `post`, which is a post-update node for a + * parameter at position `pos` in `c`. */ - predicate summarySetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) { - exists(SummaryNode mid, SummaryNode ret | - summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), mid, _) and - summaryStoreStep(mid, c, ret) - ) + private predicate isParameterPostUpdate( + SummaryNode post, SummarizedCallable c, ParameterPosition pos + ) { + post = summaryNodeOutputState(c, SummaryComponentStack::argument(pos)) } - } - /** - * Provides a means of translating externally (e.g., MaD) defined flow - * summaries into a `SummarizedCallable`s. - */ - module External { - /** Holds if `spec` is a relevant external specification. */ - private predicate relevantSpec(string spec) { - summaryElement(_, spec, _, _, _) or - summaryElement(_, _, spec, _, _) or - sourceElement(_, spec, _, _) or - sinkElement(_, spec, _, _) + /** Holds if a parameter node at position `pos` is required for `c`. */ + private predicate summaryParameterNodeRange(SummarizedCallable c, ParameterPosition pos) { + parameterReadState(c, _, pos) + or + // Same as `isParameterPostUpdate(_, c, pos)`, but can be used in a negative context + any(SummaryNodeState state).isOutputState(c, SummaryComponentStack::argument(pos)) } - private class AccessPathRange extends AccessPath::Range { - AccessPathRange() { relevantSpec(this) } + private predicate callbackOutput( + SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ReturnKind rk + ) { + any(SummaryNodeState state).isInputState(c, s) and + s.head() = TReturnSummaryComponent(rk) and + receiver = summaryNodeInputState(c, s.tail()) } - /** Holds if specification component `token` parses as parameter `pos`. */ - predicate parseParam(AccessPathToken token, ArgumentPosition pos) { - token.getName() = "Parameter" and - pos = parseParamBody(token.getAnArgument()) + private predicate callbackInput( + SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ArgumentPosition pos + ) { + any(SummaryNodeState state).isOutputState(c, s) and + s.head() = TParameterSummaryComponent(pos) and + receiver = summaryNodeInputState(c, s.tail()) } - /** Holds if specification component `token` parses as argument `pos`. */ - predicate parseArg(AccessPathToken token, ParameterPosition pos) { - token.getName() = "Argument" and - pos = parseArgBody(token.getAnArgument()) + /** Holds if a call targeting `receiver` should be synthesized inside `c`. */ + predicate summaryCallbackRange(SummarizedCallable c, SummaryNode receiver) { + callbackOutput(c, _, receiver, _) + or + callbackInput(c, _, receiver, _) } - /** Holds if specification component `token` parses as synthetic global `sg`. */ - predicate parseSynthGlobal(AccessPathToken token, string sg) { - token.getName() = "SyntheticGlobal" and - sg = token.getAnArgument() + /** Holds if summary node `p` is a parameter with position `pos`. */ + predicate summaryParameterNode(SummaryNode p, ParameterPosition pos) { + p = TSummaryParameterNode(_, pos) } - private class SyntheticGlobalFromAccessPath extends SummaryComponent::SyntheticGlobal { - SyntheticGlobalFromAccessPath() { parseSynthGlobal(_, this) } + /** Holds if summary node `out` contains output of kind `rk` from a call targeting `receiver`. */ + predicate summaryOutNode(SummaryNode receiver, SummaryNode out, ReturnKind rk) { + exists(SummarizedCallable callable, SummaryComponentStack s | + callbackOutput(callable, s, receiver, rk) and + out = summaryNodeInputState(callable, s) + ) } - private SummaryComponent interpretComponent(AccessPathToken token) { - exists(ParameterPosition pos | - parseArg(token, pos) and result = SummaryComponent::argument(pos) + /** Holds if summary node `arg` is at position `pos` in a call targeting `receiver`. */ + predicate summaryArgumentNode(SummaryNode receiver, SummaryNode arg, ArgumentPosition pos) { + exists(SummarizedCallable callable, SummaryComponentStack s | + callbackInput(callable, s, receiver, pos) and + arg = summaryNodeOutputState(callable, s) ) - or - exists(ArgumentPosition pos | - parseParam(token, pos) and result = SummaryComponent::parameter(pos) + } + + /** Holds if summary node `post` is a post-update node with pre-update node `pre`. */ + predicate summaryPostUpdateNode(SummaryNode post, SummaryNode pre) { + exists(SummarizedCallable c, ParameterPosition pos | + isParameterPostUpdate(post, c, pos) and + pre = TSummaryParameterNode(c, pos) ) or - token = "ReturnValue" and result = SummaryComponent::return(getReturnValueKind()) - or - exists(string sg | - parseSynthGlobal(token, sg) and result = SummaryComponent::syntheticGlobal(sg) + exists(SummarizedCallable callable, SummaryComponentStack s | + callbackInput(callable, s, _, _) and + pre = summaryNodeOutputState(callable, s) and + post = summaryNodeInputState(callable, s) ) - or - result = interpretComponentSpecific(token) } - /** - * Holds if `spec` specifies summary component stack `stack`. - */ - predicate interpretSpec(AccessPath spec, SummaryComponentStack stack) { - interpretSpec(spec, spec.getNumToken(), stack) + /** Holds if summary node `ret` is a return node of kind `rk`. */ + predicate summaryReturnNode(SummaryNode ret, ReturnKind rk) { + exists(SummaryComponentStack s | + ret = summaryNodeOutputState(_, s) and + s = TSingletonSummaryComponentStack(TReturnSummaryComponent(rk)) + ) } - /** Holds if the first `n` tokens of `spec` resolves to `stack`. */ - private predicate interpretSpec(AccessPath spec, int n, SummaryComponentStack stack) { - n = 1 and - stack = SummaryComponentStack::singleton(interpretComponent(spec.getToken(0))) - or - exists(SummaryComponent head, SummaryComponentStack tail | - interpretSpec(spec, n, head, tail) and - stack = SummaryComponentStack::push(head, tail) + /** + * Holds if flow is allowed to pass from the parameter at position `pos` of `c`, + * to a return node, and back out to the parameter. + */ + predicate summaryAllowParameterReturnInSelf(SummarizedCallable c, ParameterPosition ppos) { + exists(SummaryComponentStack inputContents, SummaryComponentStack outputContents | + summary(c, inputContents, outputContents, _) and + inputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) and + outputContents.bottom() = pragma[only_bind_into](TArgumentSummaryComponent(ppos)) ) } - /** Holds if the first `n` tokens of `spec` resolves to `head` followed by `tail` */ - private predicate interpretSpec( - AccessPath spec, int n, SummaryComponent head, SummaryComponentStack tail - ) { - interpretSpec(spec, n - 1, tail) and - head = interpretComponent(spec.getToken(n - 1)) + signature module StepsInputSig { + DataFlowType getSyntheticGlobalType(SyntheticGlobal sg); + + DataFlowCall getACall(SummarizedCallable sc); } - private class MkStack extends RequiredSummaryComponentStack { - override predicate required(SummaryComponent head, SummaryComponentStack tail) { - interpretSpec(_, _, head, tail) + /** Provides a compilation of flow summaries to atomic data-flow steps. */ + module Steps { + /** + * Gets the type of synthesized summary node `n`. + * + * The type is computed based on the language-specific predicates + * `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and + * `getCallbackReturnType()`. + */ + DataFlowType summaryNodeType(SummaryNode n) { + exists(SummaryNode pre | + summaryPostUpdateNode(n, pre) and + result = summaryNodeType(pre) + ) + or + exists(SummarizedCallable c, SummaryComponentStack s, SummaryComponent head | + head = s.head() + | + n = summaryNodeInputState(c, s) and + ( + exists(ContentSet cont | result = getContentType(cont) | + head = TContentSummaryComponent(cont) or + head = TWithContentSummaryComponent(cont) + ) + or + head = TWithoutContentSummaryComponent(_) and + result = summaryNodeType(summaryNodeInputState(c, s.tail())) + or + exists(ReturnKind rk | + head = TReturnSummaryComponent(rk) and + result = + getCallbackReturnType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c), + s.tail())), rk) + ) + or + exists(SyntheticGlobal sg | + head = TSyntheticGlobalSummaryComponent(sg) and + result = StepsInput::getSyntheticGlobalType(sg) + ) + or + exists(ParameterPosition pos | + head = TArgumentSummaryComponent(pos) and + result = getParameterType(c, pos) + ) + ) + or + n = summaryNodeOutputState(c, s) and + ( + exists(ContentSet cont | + head = TContentSummaryComponent(cont) and result = getContentType(cont) + ) + or + s.length() = 1 and + exists(ReturnKind rk | + head = TReturnSummaryComponent(rk) and + result = getReturnType(c, rk) + ) + or + exists(ArgumentPosition pos | head = TParameterSummaryComponent(pos) | + result = + getCallbackParameterType(summaryNodeType(summaryNodeInputState(pragma[only_bind_out](c), + s.tail())), pos) + ) + or + exists(SyntheticGlobal sg | + head = TSyntheticGlobalSummaryComponent(sg) and + result = StepsInput::getSyntheticGlobalType(sg) + ) + ) + ) } - } - private class SummarizedCallableExternal extends SummarizedCallable { - SummarizedCallableExternal() { summaryElement(this, _, _, _, _) } + /** + * Holds if there is a local step from `pred` to `succ`, which is synthesized + * from a flow summary. + */ + predicate summaryLocalStep(SummaryNode pred, SummaryNode succ, boolean preservesValue) { + exists( + SummarizedCallable c, SummaryComponentStack inputContents, + SummaryComponentStack outputContents + | + summary(c, inputContents, outputContents, preservesValue) and + pred = summaryNodeInputState(c, inputContents) and + succ = summaryNodeOutputState(c, outputContents) + | + preservesValue = true + or + preservesValue = false and not summary(c, inputContents, outputContents, true) + ) + or + exists(SummarizedCallable c, SummaryComponentStack s | + pred = summaryNodeInputState(c, s.tail()) and + succ = summaryNodeInputState(c, s) and + s.head() = [SummaryComponent::withContent(_), SummaryComponent::withoutContent(_)] and + preservesValue = true + ) + } - private predicate relevantSummaryElementGenerated( - AccessPath inSpec, AccessPath outSpec, string kind - ) { - exists(Provenance provenance | - provenance.isGenerated() and - summaryElement(this, inSpec, outSpec, kind, provenance) - ) and - not this.applyManualModel() + /** + * Holds if there is a read step of content `c` from `pred` to `succ`, which + * is synthesized from a flow summary. + */ + predicate summaryReadStep(SummaryNode pred, ContentSet c, SummaryNode succ) { + exists(SummarizedCallable sc, SummaryComponentStack s | + pred = summaryNodeInputState(sc, s.tail()) and + succ = summaryNodeInputState(sc, s) and + SummaryComponent::content(c) = s.head() + ) } - private predicate relevantSummaryElement(AccessPath inSpec, AccessPath outSpec, string kind) { - exists(Provenance provenance | - provenance.isManual() and - summaryElement(this, inSpec, outSpec, kind, provenance) + /** + * Holds if there is a store step of content `c` from `pred` to `succ`, which + * is synthesized from a flow summary. + */ + predicate summaryStoreStep(SummaryNode pred, ContentSet c, SummaryNode succ) { + exists(SummarizedCallable sc, SummaryComponentStack s | + pred = summaryNodeOutputState(sc, s) and + succ = summaryNodeOutputState(sc, s.tail()) and + SummaryComponent::content(c) = s.head() ) - or - this.relevantSummaryElementGenerated(inSpec, outSpec, kind) } - override predicate propagatesFlow( - SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + /** + * Holds if there is a jump step from `pred` to `succ`, which is synthesized + * from a flow summary. + */ + predicate summaryJumpStep(SummaryNode pred, SummaryNode succ) { + exists(SummaryComponentStack s | + s = SummaryComponentStack::singleton(SummaryComponent::syntheticGlobal(_)) and + pred = summaryNodeOutputState(_, s) and + succ = summaryNodeInputState(_, s) + ) + } + + /** + * Holds if values stored inside content `c` are cleared at `n`. `n` is a + * synthesized summary node, so in order for values to be cleared at calls + * to the relevant method, it is important that flow does not pass over + * the argument, either via use-use flow or def-use flow. + * + * Example: + * + * ``` + * a.b = taint; + * a.clearB(); // assume we have a flow summary for `clearB` that clears `b` on the qualifier + * sink(a.b); + * ``` + * + * In the above, flow should not pass from `a` on the first line (or the second + * line) to `a` on the third line. Instead, there will be synthesized flow from + * `a` on line 2 to the post-update node for `a` on that line (via an intermediate + * node where field `b` is cleared). + */ + predicate summaryClearsContent(SummaryNode n, ContentSet c) { + exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack | + n = TSummaryInternalNode(sc, state) and + state.isInputState(sc, stack) and + stack.head() = SummaryComponent::withoutContent(c) + ) + } + + /** + * Holds if the value that is being tracked is expected to be stored inside + * content `c` at `n`. + */ + predicate summaryExpectsContent(SummaryNode n, ContentSet c) { + exists(SummarizedCallable sc, SummaryNodeState state, SummaryComponentStack stack | + n = TSummaryInternalNode(sc, state) and + state.isInputState(sc, stack) and + stack.head() = SummaryComponent::withContent(c) + ) + } + + pragma[noinline] + private predicate viableParam( + DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, SummaryParamNode p ) { - exists(AccessPath inSpec, AccessPath outSpec, string kind | - this.relevantSummaryElement(inSpec, outSpec, kind) and - interpretSpec(inSpec, input) and - interpretSpec(outSpec, output) - | - kind = "value" and preservesValue = true - or - kind = "taint" and preservesValue = false + p = TSummaryParameterNode(sc, ppos) and + call = StepsInput::getACall(sc) + } + + pragma[nomagic] + private SummaryParamNode summaryArgParam(DataFlowCall call, ArgNode arg, SummarizedCallable sc) { + exists(ParameterPosition ppos | + argumentPositionMatch(call, arg, ppos) and + viableParam(call, sc, ppos, result) ) } - override predicate hasProvenance(Provenance provenance) { - summaryElement(this, _, _, _, provenance) + /** + * Holds if `p` can reach `n` in a summarized callable, using only value-preserving + * local steps. `clearsOrExpects` records whether any node on the path from `p` to + * `n` either clears or expects contents. + */ + private predicate paramReachesLocal(SummaryParamNode p, SummaryNode n, boolean clearsOrExpects) { + viableParam(_, _, _, p) and + n = p and + clearsOrExpects = false + or + exists(SummaryNode mid, boolean clearsOrExpectsMid | + paramReachesLocal(p, mid, clearsOrExpectsMid) and + summaryLocalStep(mid, n, true) and + if + summaryClearsContent(n, _) or + summaryExpectsContent(n, _) + then clearsOrExpects = true + else clearsOrExpects = clearsOrExpectsMid + ) } - } - /** Holds if component `c` of specification `spec` cannot be parsed. */ - predicate invalidSpecComponent(AccessPath spec, string c) { - c = spec.getToken(_) and - not exists(interpretComponent(c)) - } + /** + * Holds if use-use flow starting from `arg` should be prohibited. + * + * This is the case when `arg` is the argument of a call that targets a + * flow summary where the corresponding parameter either clears contents + * or expects contents. + */ + pragma[nomagic] + predicate prohibitsUseUseFlow(ArgNode arg, SummarizedCallable sc) { + exists(SummaryParamNode p, ParameterPosition ppos, SummaryNode ret | + paramReachesLocal(p, ret, true) and + p = summaryArgParam(_, arg, sc) and + p = TSummaryParameterNode(_, pragma[only_bind_into](ppos)) and + isParameterPostUpdate(ret, _, pragma[only_bind_into](ppos)) + ) + } - /** Holds if `provenance` is not a valid provenance value. */ - bindingset[provenance] - predicate invalidProvenance(string provenance) { not provenance instanceof Provenance } + pragma[nomagic] + private predicate summaryReturnNodeExt(SummaryNode ret, ReturnKindExt rk) { + summaryReturnNode(ret, rk.(ValueReturnKind).getKind()) + or + exists(SummaryParamNode p, SummaryNode pre, ParameterPosition pos | + paramReachesLocal(p, pre, _) and + summaryPostUpdateNode(ret, pre) and + p = TSummaryParameterNode(_, pos) and + rk.(ParamUpdateReturnKind).getPosition() = pos + ) + } - /** - * Holds if token `part` of specification `spec` has an invalid index. - * E.g., `Argument[-1]`. - */ - predicate invalidIndexComponent(AccessPath spec, AccessPathToken part) { - part = spec.getToken(_) and - part.getName() = ["Parameter", "Argument"] and - AccessPath::parseInt(part.getArgumentList()) < 0 - } + bindingset[ret] + private SummaryParamNode summaryArgParamRetOut( + ArgNode arg, SummaryNode ret, OutNodeExt out, SummarizedCallable sc + ) { + exists(DataFlowCall call, ReturnKindExt rk | + result = summaryArgParam(call, arg, sc) and + summaryReturnNodeExt(ret, pragma[only_bind_into](rk)) and + out = pragma[only_bind_into](rk).getAnOutNode(call) + ) + } - private predicate inputNeedsReference(AccessPathToken c) { - c.getName() = "Argument" or - inputNeedsReferenceSpecific(c) - } + /** + * Holds if `arg` flows to `out` using a simple value-preserving flow + * summary, that is, a flow summary without reads and stores. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summaryThroughStepValue(ArgNode arg, Node out, SummarizedCallable sc) { + exists(ReturnKind rk, SummaryNode ret, DataFlowCall call | + summaryLocalStep(summaryArgParam(call, arg, sc), ret, true) and + summaryReturnNode(ret, pragma[only_bind_into](rk)) and + out = getAnOutNode(call, pragma[only_bind_into](rk)) + ) + } - private predicate outputNeedsReference(AccessPathToken c) { - c.getName() = ["Argument", "ReturnValue"] or - outputNeedsReferenceSpecific(c) - } + /** + * Holds if `arg` flows to `out` using a simple flow summary involving taint + * step, that is, a flow summary without reads and stores. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summaryThroughStepTaint(ArgNode arg, Node out, SummarizedCallable sc) { + exists(SummaryNode ret | + summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), ret, false) + ) + } - private predicate sourceElementRef(InterpretNode ref, AccessPath output, string kind) { - exists(SourceOrSinkElement e | - sourceElement(e, output, kind, _) and - if outputNeedsReference(output.getToken(0)) - then e = ref.getCallTarget() - else e = ref.asElement() - ) - } + /** + * Holds if there is a read(+taint) of `c` from `arg` to `out` using a + * flow summary. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summaryGetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) { + exists(SummaryNode mid, SummaryNode ret | + summaryReadStep(summaryArgParamRetOut(arg, ret, out, sc), c, mid) and + summaryLocalStep(mid, ret, _) + ) + } - private predicate sinkElementRef(InterpretNode ref, AccessPath input, string kind) { - exists(SourceOrSinkElement e | - sinkElement(e, input, kind, _) and - if inputNeedsReference(input.getToken(0)) - then e = ref.getCallTarget() - else e = ref.asElement() - ) + /** + * Holds if there is a (taint+)store of `arg` into content `c` of `out` using a + * flow summary. + * + * NOTE: This step should not be used in global data-flow/taint-tracking, but may + * be useful to include in the exposed local data-flow/taint-tracking relations. + */ + predicate summarySetterStep(ArgNode arg, ContentSet c, Node out, SummarizedCallable sc) { + exists(SummaryNode mid, SummaryNode ret | + summaryLocalStep(summaryArgParamRetOut(arg, ret, out, sc), mid, _) and + summaryStoreStep(mid, c, ret) + ) + } } - /** Holds if the first `n` tokens of `output` resolve to the given interpretation. */ - private predicate interpretOutput( - AccessPath output, int n, InterpretNode ref, InterpretNode node - ) { - sourceElementRef(ref, output, _) and - n = 0 and - ( - if output = "" - then - // Allow language-specific interpretation of the empty access path - interpretOutputSpecific("", ref, node) - else node = ref - ) - or - exists(InterpretNode mid, AccessPathToken c | - interpretOutput(output, n - 1, ref, mid) and - c = output.getToken(n - 1) - | - exists(ArgumentPosition apos, ParameterPosition ppos | - node.asNode().(PostUpdateNode).getPreUpdateNode().(ArgNode).argumentOf(mid.asCall(), apos) and - parameterMatch(ppos, apos) + /** + * Provides a means of translating externally (e.g., MaD) defined flow + * summaries into a `SummarizedCallable`s. + */ + module External { + private SummaryComponent interpretComponent(AccessPathToken token) { + exists(ContentSet c | + exists(string name | name = encodeContent(c, token.getAnArgument(name))) + or + token = encodeContent(c, "") | - c = "Argument" or parseArg(c, ppos) + result = SummaryComponent::content(c) ) or - exists(ArgumentPosition apos, ParameterPosition ppos | - node.asNode().(ParamNode).isParameterOf(mid.asCallable(), ppos) and - parameterMatch(ppos, apos) - | - c = "Parameter" or parseParam(c, apos) + exists(ParameterPosition pos | + parseArg(token, pos) and + result = SummaryComponent::argument(pos) ) or - c = "ReturnValue" and - node.asNode() = getAnOutNodeExt(mid.asCall(), TValueReturn(getReturnValueKind())) + exists(ArgumentPosition pos | + parseParam(token, pos) and result = SummaryComponent::parameter(pos) + ) or - interpretOutputSpecific(c, mid, node) - ) - } - - /** Holds if the first `n` tokens of `input` resolve to the given interpretation. */ - private predicate interpretInput(AccessPath input, int n, InterpretNode ref, InterpretNode node) { - sinkElementRef(ref, input, _) and - n = 0 and - ( - if input = "" - then - // Allow language-specific interpretation of the empty access path - interpretInputSpecific("", ref, node) - else node = ref - ) - or - exists(InterpretNode mid, AccessPathToken c | - interpretInput(input, n - 1, ref, mid) and - c = input.getToken(n - 1) - | - exists(ArgumentPosition apos, ParameterPosition ppos | - node.asNode().(ArgNode).argumentOf(mid.asCall(), apos) and - parameterMatch(ppos, apos) + token = "ReturnValue" and result = SummaryComponent::return(getStandardReturnValueKind()) + or + exists(ReturnKind rk | + exists(string name | name = encodeReturn(rk, token.getAnArgument(name))) or + token = encodeReturn(rk, "") | - c = "Argument" or parseArg(c, ppos) + result = SummaryComponent::return(rk) ) or - exists(ReturnNodeExt ret | - c = "ReturnValue" and - ret = node.asNode() and - ret.getKind().(ValueReturnKind).getKind() = getReturnValueKind() and - mid.asCallable() = getNodeEnclosingCallable(ret) + exists(string sg | + parseSynthGlobal(token, sg) and result = SummaryComponent::syntheticGlobal(sg) ) or - interpretInputSpecific(c, mid, node) - ) - } - - /** - * Holds if `node` is specified as a source with the given kind in a MaD flow - * model. - */ - predicate isSourceNode(InterpretNode node, string kind) { - exists(InterpretNode ref, AccessPath output | - sourceElementRef(ref, output, kind) and - interpretOutput(output, output.getNumToken(), ref, node) - ) - } + exists(ContentSet c | + exists(string name | name = encodeWithoutContent(c, token.getAnArgument(name))) + or + token = encodeWithoutContent(c, "") + | + result = SummaryComponent::withoutContent(c) + ) + or + exists(ContentSet c | + exists(string name | name = encodeWithContent(c, token.getAnArgument(name))) or + token = encodeWithContent(c, "") + | + result = SummaryComponent::withContent(c) + ) + } - /** - * Holds if `node` is specified as a sink with the given kind in a MaD flow - * model. - */ - predicate isSinkNode(InterpretNode node, string kind) { - exists(InterpretNode ref, AccessPath input | - sinkElementRef(ref, input, kind) and - interpretInput(input, input.getNumToken(), ref, node) - ) - } - } + /** + * Holds if `spec` specifies summary component stack `stack`. + */ + predicate interpretSpec(AccessPath spec, SummaryComponentStack stack) { + interpretSpec(spec, spec.getNumToken(), stack) + } - /** Provides a query predicate for outputting a set of relevant flow summaries. */ - module TestOutput { - /** A flow summary to include in the `summary/1` query predicate. */ - abstract class RelevantSummarizedCallable instanceof SummarizedCallable { - /** Gets the string representation of this callable used by `summary/1`. */ - abstract string getCallableCsv(); + /** Holds if the first `n` tokens of `spec` resolves to `stack`. */ + private predicate interpretSpec(AccessPath spec, int n, SummaryComponentStack stack) { + n = 1 and + stack = SummaryComponentStack::singleton(interpretComponent(spec.getToken(0))) + or + exists(SummaryComponent head, SummaryComponentStack tail | + interpretSpec(spec, n, head, tail) and + stack = SummaryComponentStack::push(head, tail) + ) + } - /** Holds if flow is propagated between `input` and `output`. */ - predicate relevantSummary( - SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + /** Holds if the first `n` tokens of `spec` resolves to `head` followed by `tail` */ + private predicate interpretSpec( + AccessPath spec, int n, SummaryComponent head, SummaryComponentStack tail ) { - super.propagatesFlow(input, output, preservesValue) + interpretSpec(spec, n - 1, tail) and + head = interpretComponent(spec.getToken(n - 1)) } - string toString() { result = super.toString() } - } + private class MkStack extends RequiredSummaryComponentStack { + override predicate required(SummaryComponent head, SummaryComponentStack tail) { + interpretSpec(_, _, head, tail) + } + } + + private class SummarizedCallableExternal extends SummarizedCallableImpl instanceof SummarizedCallable + { + SummarizedCallableExternal() { summaryElement(this, _, _, _, _) } + + private predicate relevantSummaryElementGenerated( + AccessPath inSpec, AccessPath outSpec, boolean preservesValue + ) { + exists(Provenance provenance | + provenance.isGenerated() and + summaryElement(this, inSpec, outSpec, preservesValue, provenance) + ) and + not super.applyManualModel() + } + + private predicate relevantSummaryElement( + AccessPath inSpec, AccessPath outSpec, boolean preservesValue + ) { + exists(Provenance provenance | + provenance.isManual() and + summaryElement(this, inSpec, outSpec, preservesValue, provenance) + ) + or + this.relevantSummaryElementGenerated(inSpec, outSpec, preservesValue) + } + + override predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + exists(AccessPath inSpec, AccessPath outSpec | + this.relevantSummaryElement(inSpec, outSpec, preservesValue) and + interpretSpec(inSpec, input) and + interpretSpec(outSpec, output) + ) + } + + override predicate hasProvenance(Provenance provenance) { + summaryElement(this, _, _, _, provenance) + } + } + + /** Holds if component `c` of specification `spec` cannot be parsed. */ + predicate invalidSpecComponent(AccessPath spec, string c) { + c = spec.getToken(_) and + not exists(interpretComponent(c)) + } - /** A model to include in the `neutral/1` query predicate. */ - abstract class RelevantNeutralCallable instanceof NeutralCallable { - /** Gets the string representation of this callable used by `neutral/1`. */ - abstract string getCallableCsv(); + /** Holds if `provenance` is not a valid provenance value. */ + bindingset[provenance] + predicate invalidProvenance(string provenance) { not provenance instanceof Provenance } /** - * Gets the kind of the neutral. + * Holds if token `part` of specification `spec` has an invalid index. + * E.g., `Argument[-1]`. */ - string getKind() { result = super.getKind() } + predicate invalidIndexComponent(AccessPath spec, AccessPathToken part) { + part = spec.getToken(_) and + part.getName() = ["Parameter", "Argument"] and + AccessPathSyntax::parseInt(part.getArgumentList()) < 0 + } - string toString() { result = super.toString() } - } + signature module SourceSinkInterpretationInputSig { + class Element { + string toString(); + } - /** Render the kind in the format used in flow summaries. */ - private string renderKind(boolean preservesValue) { - preservesValue = true and result = "value" - or - preservesValue = false and result = "taint" - } + /** + * Holds if an external source specification exists for `n` with output specification + * `output`, kind `kind`, and provenance `provenance`. + */ + predicate sourceElement(Element n, string output, string kind, string provenance); - private string renderProvenance(SummarizedCallable c) { - if c.applyManualModel() then result = "manual" else c.hasProvenance(result) - } + /** + * Holds if an external sink specification exists for `n` with input specification + * `input`, kind `kind` and provenance `provenance`. + */ + predicate sinkElement(Element n, string input, string kind, string provenance); - private string renderProvenanceNeutral(NeutralCallable c) { - if c.hasManualModel() then result = "manual" else c.hasProvenance(result) - } + class SourceOrSinkElement extends Element; - /** - * A query predicate for outputting flow summaries in semi-colon separated format in QL tests. - * The syntax is: "namespace;type;overrides;name;signature;ext;inputspec;outputspec;kind;provenance", - * ext is hardcoded to empty. - */ - query predicate summary(string csv) { - exists( - RelevantSummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output, - boolean preservesValue - | - c.relevantSummary(input, output, preservesValue) and - csv = - c.getCallableCsv() // Callable information - + input.getMadRepresentation() + ";" // input - + output.getMadRepresentation() + ";" // output - + renderKind(preservesValue) + ";" // kind - + renderProvenance(c) // provenance - ) - } + /** An entity used to interpret a source/sink specification. */ + class InterpretNode extends Element { + /** Gets the element that this node corresponds to, if any. */ + SourceOrSinkElement asElement(); - /** - * Holds if a neutral model `csv` exists (semi-colon separated format). Used for testing purposes. - * The syntax is: "namespace;type;name;signature;kind;provenance"", - */ - query predicate neutral(string csv) { - exists(RelevantNeutralCallable c | - csv = - c.getCallableCsv() // Callable information - + c.getKind() + ";" // kind - + renderProvenanceNeutral(c) // provenance - ) - } - } + /** Gets the data-flow node that this node corresponds to, if any. */ + DataFlowLang::Node asNode(); - /** - * Provides query predicates for rendering the generated data flow graph for - * a summarized callable. - * - * Import this module into a `.ql` file of `@kind graph` to render the graph. - * The graph is restricted to callables from `RelevantSummarizedCallable`. - */ - module RenderSummarizedCallable { - /** A summarized callable to include in the graph. */ - abstract class RelevantSummarizedCallable instanceof SummarizedCallable { - string toString() { result = super.toString() } - } + /** Gets the call that this node corresponds to, if any. */ + DataFlowLang::DataFlowCall asCall(); + + /** Gets the callable that this node corresponds to, if any. */ + DataFlowLang::DataFlowCallable asCallable(); + + /** Gets the target of this call, if any. */ + Element getCallTarget(); + } + + /** Provides additional sink specification logic. */ + predicate interpretOutput(string c, InterpretNode mid, InterpretNode node); + + /** Provides additional source specification logic. */ + predicate interpretInput(string c, InterpretNode mid, InterpretNode node); - private newtype TNodeOrCall = - MkNode(SummaryNode n) { - exists(RelevantSummarizedCallable c | - n = TSummaryInternalNode(c, _) + /** Holds if output specification component `c` needs a reference. */ + predicate outputNeedsReference(string c); + + /** Holds if input specification component `c` needs a reference. */ + predicate inputNeedsReference(string c); + } + + /** + * Legacy interface for interpreting source/sink specifications in Java and C#. + * + * Should eventually be replaced with API graphs like in dynamic languages. + */ + module SourceSinkInterpretation< + SourceSinkInterpretationInputSig SourceSinkInterpretationInput> + { + private import SourceSinkInterpretationInput + + private predicate sourceSinkSpec(string spec) { + sourceElement(_, spec, _, _) or + sinkElement(_, spec, _, _) + } + + private module AccessPath = AccessPathSyntax::AccessPath; + + private class SourceSinkAccessPathToken = AccessPath::AccessPathToken; + + private class SourceSinkAccessPath = AccessPath::AccessPath; + + private predicate outputNeedsReferenceExt(AccessPathToken c) { + c.getName() = ["Argument", "ReturnValue"] or + outputNeedsReference(c) + } + + private predicate sourceElementRef( + InterpretNode ref, SourceSinkAccessPath output, string kind + ) { + exists(SourceOrSinkElement e | + sourceElement(e, output, kind, _) and + if outputNeedsReferenceExt(output.getToken(0)) + then e = ref.getCallTarget() + else e = ref.asElement() + ) + } + + private predicate inputNeedsReferenceExt(SourceSinkAccessPathToken c) { + c.getName() = "Argument" or + inputNeedsReference(c) + } + + private predicate sinkElementRef(InterpretNode ref, SourceSinkAccessPath input, string kind) { + exists(SourceOrSinkElement e | + sinkElement(e, input, kind, _) and + if inputNeedsReferenceExt(input.getToken(0)) + then e = ref.getCallTarget() + else e = ref.asElement() + ) + } + + /** Holds if the first `n` tokens of `output` resolve to the given interpretation. */ + private predicate interpretOutput( + SourceSinkAccessPath output, int n, InterpretNode ref, InterpretNode node + ) { + sourceElementRef(ref, output, _) and + n = 0 and + ( + if output = "" + then + // Allow language-specific interpretation of the empty access path + SourceSinkInterpretationInput::interpretOutput("", ref, node) + else node = ref + ) or - n = TSummaryParameterNode(c, _) - ) - } or - MkCall(DataFlowCall call) { - call = summaryDataFlowCall(_) and - call.getEnclosingCallable() = inject(any(RelevantSummarizedCallable c)) + exists(InterpretNode mid, SourceSinkAccessPathToken c | + interpretOutput(output, n - 1, ref, mid) and + c = output.getToken(n - 1) + | + exists(ArgumentPosition apos, ParameterPosition ppos | + node.asNode() + .(PostUpdateNode) + .getPreUpdateNode() + .(ArgNode) + .argumentOf(mid.asCall(), apos) and + parameterMatch(ppos, apos) + | + c = "Argument" or parseArg(c, ppos) + ) + or + exists(ArgumentPosition apos, ParameterPosition ppos | + node.asNode().(ParamNode).isParameterOf(mid.asCallable(), ppos) and + parameterMatch(ppos, apos) + | + c = "Parameter" or parseParam(c, apos) + ) + or + c = "ReturnValue" and + node.asNode() = + getAnOutNodeExt(mid.asCall(), TValueReturn(getStandardReturnValueKind())) + or + SourceSinkInterpretationInput::interpretOutput(c, mid, node) + ) + } + + /** Holds if the first `n` tokens of `input` resolve to the given interpretation. */ + private predicate interpretInput( + SourceSinkAccessPath input, int n, InterpretNode ref, InterpretNode node + ) { + sinkElementRef(ref, input, _) and + n = 0 and + ( + if input = "" + then + // Allow language-specific interpretation of the empty access path + SourceSinkInterpretationInput::interpretInput("", ref, node) + else node = ref + ) + or + exists(InterpretNode mid, SourceSinkAccessPathToken c | + interpretInput(input, n - 1, ref, mid) and + c = input.getToken(n - 1) + | + exists(ArgumentPosition apos, ParameterPosition ppos | + node.asNode().(ArgNode).argumentOf(mid.asCall(), apos) and + parameterMatch(ppos, apos) + | + c = "Argument" or parseArg(c, ppos) + ) + or + exists(ReturnNodeExt ret | + c = "ReturnValue" and + ret = node.asNode() and + ret.getKind().(ValueReturnKind).getKind() = getStandardReturnValueKind() and + mid.asCallable() = getNodeEnclosingCallable(ret) + ) + or + SourceSinkInterpretationInput::interpretInput(c, mid, node) + ) + } + + /** + * Holds if `node` is specified as a source with the given kind in a MaD flow + * model. + */ + predicate isSourceNode(InterpretNode node, string kind) { + exists(InterpretNode ref, SourceSinkAccessPath output | + sourceElementRef(ref, output, kind) and + interpretOutput(output, output.getNumToken(), ref, node) + ) + } + + /** + * Holds if `node` is specified as a sink with the given kind in a MaD flow + * model. + */ + predicate isSinkNode(InterpretNode node, string kind) { + exists(InterpretNode ref, SourceSinkAccessPath input | + sinkElementRef(ref, input, kind) and + interpretInput(input, input.getNumToken(), ref, node) + ) + } } + } - private class NodeOrCall extends TNodeOrCall { - SummaryNode asNode() { this = MkNode(result) } + /** Provides a query predicate for outputting a set of relevant flow summaries. */ + module TestOutput { + /** A flow summary to include in the `summary/1` query predicate. */ + abstract class RelevantSummarizedCallable instanceof SummarizedCallableImpl { + /** Gets the string representation of this callable used by `summary/1`. */ + abstract string getCallableCsv(); - DataFlowCall asCall() { this = MkCall(result) } + /** Holds if flow is propagated between `input` and `output`. */ + predicate relevantSummary( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + super.propagatesFlow(input, output, preservesValue) + } - string toString() { - result = this.asNode().toString() + string toString() { result = super.toString() } + } + + /** A model to include in the `neutral/1` query predicate. */ + abstract class RelevantNeutralCallable instanceof NeutralCallable { + /** Gets the string representation of this callable used by `neutral/1`. */ + abstract string getCallableCsv(); + + /** + * Gets the kind of the neutral. + */ + string getKind() { result = super.getKind() } + + string toString() { result = super.toString() } + } + + /** Render the kind in the format used in flow summaries. */ + private string renderKind(boolean preservesValue) { + preservesValue = true and result = "value" or - result = this.asCall().toString() + preservesValue = false and result = "taint" + } + + private string renderProvenance(SummarizedCallable c) { + if c.applyManualModel() then result = "manual" else c.hasProvenance(result) + } + + private string renderProvenanceNeutral(NeutralCallable c) { + if c.hasManualModel() then result = "manual" else c.hasProvenance(result) } /** - * Holds if this element is at the specified location. - * The location spans column `startcolumn` of line `startline` to - * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see - * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + * A query predicate for outputting flow summaries in semi-colon separated format in QL tests. + * The syntax is: "namespace;type;overrides;name;signature;ext;inputspec;outputspec;kind;provenance", + * ext is hardcoded to empty. */ - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - filepath = "" and - startline = 0 and - startcolumn = 0 and - endline = 0 and - endcolumn = 0 + query predicate summary(string csv) { + exists( + RelevantSummarizedCallable c, SummaryComponentStack input, SummaryComponentStack output, + boolean preservesValue + | + c.relevantSummary(input, output, preservesValue) and + csv = + c.getCallableCsv() // Callable information + + input.getMadRepresentation() + ";" // input + + output.getMadRepresentation() + ";" // output + + renderKind(preservesValue) + ";" // kind + + renderProvenance(c) // provenance + ) } - } - query predicate nodes(NodeOrCall n, string key, string val) { - key = "semmle.label" and val = n.toString() + /** + * Holds if a neutral model `csv` exists (semi-colon separated format). Used for testing purposes. + * The syntax is: "namespace;type;name;signature;kind;provenance"", + */ + query predicate neutral(string csv) { + exists(RelevantNeutralCallable c | + csv = + c.getCallableCsv() // Callable information + + c.getKind() + ";" // kind + + renderProvenanceNeutral(c) // provenance + ) + } } - private predicate edgesComponent(NodeOrCall a, NodeOrCall b, string value) { - exists(boolean preservesValue | - Private::Steps::summaryLocalStep(a.asNode(), b.asNode(), preservesValue) and - if preservesValue = true then value = "value" else value = "taint" - ) - or - exists(ContentSet c | - Private::Steps::summaryReadStep(a.asNode(), c, b.asNode()) and - value = "read (" + c + ")" + /** + * Provides query predicates for rendering the generated data flow graph for + * a summarized callable. + * + * Import this module into a `.ql` file of `@kind graph` to render the graph. + * The graph is restricted to callables from `RelevantSummarizedCallable`. + */ + module RenderSummarizedCallable { + private module PrivateSteps = Private::Steps; + + /** A summarized callable to include in the graph. */ + abstract class RelevantSummarizedCallable instanceof SummarizedCallable { + string toString() { result = super.toString() } + } + + private newtype TNodeOrCall = + MkNode(SummaryNode n) { + exists(RelevantSummarizedCallable c | + n = TSummaryInternalNode(c, _) + or + n = TSummaryParameterNode(c, _) + ) + } or + MkCall(SummaryNode receiver) { + receiver.getSummarizedCallable() instanceof RelevantSummarizedCallable and + ( + callbackInput(_, _, receiver, _) or + callbackOutput(_, _, receiver, _) + ) + } + + private class NodeOrCall extends TNodeOrCall { + SummaryNode asNode() { this = MkNode(result) } + + SummaryNode asCallReceiver() { this = MkCall(result) } + + string toString() { + result = this.asNode().toString() + or + result = this.asCallReceiver().toString() + } + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + filepath = "" and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } + } + + query predicate nodes(NodeOrCall n, string key, string val) { + key = "semmle.label" and val = n.toString() + } + + private predicate edgesComponent(NodeOrCall a, NodeOrCall b, string value) { + exists(boolean preservesValue | + PrivateSteps::summaryLocalStep(a.asNode(), b.asNode(), preservesValue) and + if preservesValue = true then value = "value" else value = "taint" + ) or - Private::Steps::summaryStoreStep(a.asNode(), c, b.asNode()) and - value = "store (" + c + ")" + exists(ContentSet c | + PrivateSteps::summaryReadStep(a.asNode(), c, b.asNode()) and + value = "read (" + c + ")" + or + PrivateSteps::summaryStoreStep(a.asNode(), c, b.asNode()) and + value = "store (" + c + ")" + or + PrivateSteps::summaryClearsContent(a.asNode(), c) and + b = a and + value = "clear (" + c + ")" + or + PrivateSteps::summaryExpectsContent(a.asNode(), c) and + b = a and + value = "expect (" + c + ")" + ) or - Private::Steps::summaryClearsContent(a.asNode(), c) and - b = a and - value = "clear (" + c + ")" + summaryPostUpdateNode(b.asNode(), a.asNode()) and + value = "post-update" or - Private::Steps::summaryExpectsContent(a.asNode(), c) and - b = a and - value = "expect (" + c + ")" - ) - or - summaryPostUpdateNode(b.asNode(), a.asNode()) and - value = "post-update" - or - b.asCall() = summaryDataFlowCall(a.asNode()) and - value = "receiver" - or - exists(ArgumentPosition pos | - summaryArgumentNode(b.asCall(), a.asNode(), pos) and - value = "argument (" + pos + ")" - ) - } + b.asCallReceiver() = a.asNode() and + value = "receiver" + or + exists(ArgumentPosition pos | + summaryArgumentNode(b.asCallReceiver(), a.asNode(), pos) and + value = "argument (" + pos + ")" + ) + } - query predicate edges(NodeOrCall a, NodeOrCall b, string key, string value) { - key = "semmle.label" and - value = strictconcat(string s | edgesComponent(a, b, s) | s, " / ") + query predicate edges(NodeOrCall a, NodeOrCall b, string key, string value) { + key = "semmle.label" and + value = strictconcat(string s | edgesComponent(a, b, s) | s, " / ") + } } } }