From 741725bb2b751217b5d795eda20efa64f326015d Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Tue, 24 Oct 2023 13:34:34 +0200 Subject: [PATCH] sdf --- config/identical-files.json | 3 +- .../lib/codeql/ruby/dataflow/FlowSummary.qll | 3 +- .../dataflow/internal/FlowSummaryImpl.qll | 196 +- .../data/internal/ApiGraphModelsSpecific.qll | 8 +- .../dataflow/internal/FlowSummaryImpl.qll | 2942 ++++++++--------- 5 files changed, 1485 insertions(+), 1667 deletions(-) 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/lib/codeql/ruby/dataflow/FlowSummary.qll b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll index 19361ce6d81f2..d7ba0898335aa 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/FlowSummary.qll @@ -104,6 +104,7 @@ deprecated class SummaryComponentStack = Impl::Private::SummaryComponentStack; /** * DEPRECATED. + * * Provides predicates for constructing stacks of summary components. */ deprecated module SummaryComponentStack { @@ -145,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 = Impl::InputStep1::getParameterPosition(pos) + s = Impl::Input::encodeParameterPosition(pos) ) } } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll index c0a3c2d08abc9..413dd5d361591 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImpl.qll @@ -9,12 +9,9 @@ private import codeql.ruby.dataflow.internal.DataFlowImplSpecific as DataFlowImp private import DataFlowImplSpecific::Private private import DataFlowImplSpecific::Public -module InputStep1 implements InputStep1Sig { +module Input implements InputSig { class SummarizedCallableBase = string; - bindingset[c] - string summarizedCallableBaseToString(SummarizedCallableBase c) { result = c } - ArgumentPosition callbackSelfParameterPosition() { result.isLambdaSelf() } DataFlowType getContentType(ContentSet c) { result = TUnknownDataFlowType() and exists(c) } @@ -39,7 +36,9 @@ module InputStep1 implements InputStep1Sig { result = TUnknownDataFlowType() and exists(t) and exists(rk) } - string getParameterPosition(ParameterPosition pos) { + ReturnKind getStandardReturnValueKind() { result instanceof NormalReturnKind } + + string encodeParameterPosition(ParameterPosition pos) { exists(int i | pos.isPositional(i) and result = i.toString() @@ -77,7 +76,7 @@ module InputStep1 implements InputStep1Sig { result = "splat" } - string getArgumentPosition(ArgumentPosition pos) { + string encodeArgumentPosition(ArgumentPosition pos) { pos.isSelf() and result = "self" or pos.isLambdaSelf() and result = "lambda-self" @@ -93,182 +92,64 @@ module InputStep1 implements InputStep1Sig { pos.isKeyword(name) and result = name + ":" ) - } - - ReturnKind getReturnValueKind() { result instanceof NormalReturnKind } - - bindingset[s] - ArgumentPosition parseParamBody(string s) { - result.isPositional(AccessPath::parseInt(s)) - or - exists(string name | - 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() - } - - bindingset[s] - ParameterPosition parseArgBody(string s) { - result.isPositional(AccessPath::parseInt(s)) - or - result.isPositionalLowerBound(AccessPath::parseLowerBound(s)) - or - exists(string name | - 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() - } -} - -import MakeStep1 - -module InputStep2 implements InputStep2Sig { - bindingset[arg] - private ContentSet interpretElementArg(string arg) { - arg = "?" and - result = TSingletonContent(TUnknownElementContent()) or - arg = "any" and - result = TAnyElementContent() - or - exists(int lower, boolean includeUnknown | - lower = - AccessPath::parseLowerBound(ParsePositions::adjustElementArgument(arg, includeUnknown)) and - result = TElementLowerBoundContent(lower, includeUnknown) - ) - or - exists(ConstantValue cv, string argAdjusted, boolean includeUnknown | - argAdjusted = ParsePositions::adjustElementArgument(arg, includeUnknown) and - ( - includeUnknown = false and - result = TSingletonContent(TKnownElementContent(cv)) - or - includeUnknown = true and - result = TKnownOrUnknownElementContent(TKnownElementContent(cv)) - ) - | - cv.isInt(AccessPath::parseInt(argAdjusted)) - or - not exists(AccessPath::parseInt(argAdjusted)) and - cv.serialize() = argAdjusted - ) - } - - bindingset[c] - ContentSet interpretContentSummaryComponent(AccessPath::AccessPathTokenBase c) { - result = interpretElementArg(c.getAnArgument("Element")) + pos.isAny() and + result = "any" or - result = TSingletonContent(TFieldContent(c.getAnArgument("Field"))) + pos.isAnyNamed() and + result = "any-named" } - bindingset[c] - ArgumentPosition interpretParameterSummaryComponent(AccessPath::AccessPathTokenBase c) { none() } - - bindingset[c] - ParameterPosition interpretArgumentSummaryComponent(AccessPath::AccessPathTokenBase c) { - exists(string arg | arg = c.getAnArgument("Argument") | - arg = "any" and - result.isAny() - or - result.isPositionalLowerBound(AccessPath::parseLowerBound(arg)) + string encodeContent(ContentSet cs, string arg) { + exists(Content c | cs = TSingletonContent(c) | + c = TFieldContent(arg) and result = "Field" or - arg = "hash-splat" and - result.isHashSplat() + exists(ConstantValue cv | + c = TKnownElementContent(cv) and + result = "Element" and + arg = cv.serialize() + "!" + ) or - arg = "splat" and - result.isSplat(0) - ) - } - - bindingset[c] - ContentSet interpretWithoutContentSummaryComponent(AccessPath::AccessPathTokenBase c) { - result = interpretElementArg(c.getAnArgument("WithoutElement")) - } - - bindingset[c] - ContentSet interpretWithContentSummaryComponent(AccessPath::AccessPathTokenBase c) { - result = interpretElementArg(c.getAnArgument("WithElement")) - } - - 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() + "!]" + c = TUnknownElementContent() and result = "Element" and arg = "?" ) or - c = TUnknownElementContent() and result = "Element[?]" - } - - string getContentMadRepresentation(ContentSet cs) { - exists(Content c | cs = TSingletonContent(c) and result = getContentSpecific(c)) - or - cs = TAnyElementContent() and result = "Element[any]" + cs = TAnyElementContent() and result = "Element" and arg = "any" or exists(Content::KnownElementContent kec | cs = TKnownOrUnknownElementContent(kec) and - result = "Element[" + kec.getIndex().serialize() + "]" + result = "Element" and + arg = 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 + "]" + result = "Element" and + arg = lower.toString() + ".." + unknown ) } - string getReturnMadRepresentation(ReturnKind rk) { - not rk = InputStep1::getReturnValueKind() and - result = "ReturnValue[" + rk + "]" + string encodeReturn(ReturnKind rk, string arg) { + not rk = Input::getStandardReturnValueKind() and + result = "ReturnValue" and + arg = rk.toString() } - string getWithoutContentMadRepresentation(ContentSet c) { - result = "WithoutElement[" + getContentMadRepresentation(c) + "]" + string encodeWithoutContent(ContentSet c, string arg) { + result = "Without" + encodeContent(c, arg) } - string getWithContentMadRepresentation(ContentSet c) { - result = "WithElement[" + getContentMadRepresentation(c) + "]" - } + string encodeWithContent(ContentSet c, string arg) { result = "With" + encodeContent(c, arg) } } -import MakeStep2 as Step2 +private import Make as Impl -module InputStep3 implements Step2::Private::InputStep3Sig { - DataFlowType getSyntheticGlobalType(Step2::Private::SyntheticGlobal sg) { +private module StepsInput implements Impl::Private::StepsInputSig { + DataFlowType getSyntheticGlobalType(Private::SyntheticGlobal sg) { result = TUnknownDataFlowType() and exists(sg) } - DataFlowCall getACall(Step2::Public::SummarizedCallable sc) { + DataFlowCall getACall(Public::SummarizedCallable sc) { result.asCall().getAstNode() = sc.(LibraryCallable).getACall() or result.asCall().getAstNode() = sc.(LibraryCallable).getACallSimple() @@ -276,11 +157,12 @@ module InputStep3 implements Step2::Private::InputStep3Sig { } module Private { - import Step2::Private - import MakeStep3 + import Impl::Private + + module Steps = Impl::Private::Steps; } -module Public = Step2::Public; +module Public = Impl::Public; module ParsePositions { private import Private @@ -323,7 +205,7 @@ module ParsePositions { } bindingset[arg] - string adjustElementArgument(string arg, boolean includeUnknown) { + private string adjustElementArgument(string arg, boolean includeUnknown) { result = arg.regexpCapture("(.*)!", 1) and includeUnknown = false or 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 98446702c3fee..5481030c7de40 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsSpecific.qll @@ -150,13 +150,13 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) { or token.getName() = "Parameter" and exists(DataFlowDispatch::ArgumentPosition argPos, DataFlowDispatch::ParameterPosition paramPos | - argPos = FlowSummaryImpl::InputStep1::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 | - contents = FlowSummaryImpl::InputStep2::interpretContentSummaryComponent(token) and + exists(DataFlow::ContentSet contents, string name | + name = FlowSummaryImpl::Input::encodeContent(contents, token.getAnArgument(name)) and result = node.getContents(contents) ) } @@ -168,7 +168,7 @@ bindingset[token] API::Node getExtraSuccessorFromInvoke(InvokeNode node, AccessPathTokenBase token) { token.getName() = "Argument" and exists(DataFlowDispatch::ArgumentPosition argPos, DataFlowDispatch::ParameterPosition paramPos | - paramPos = FlowSummaryImpl::InputStep1::parseArgBody(token.getAnArgument()) and + token.getAnArgument() = FlowSummaryImpl::Input::encodeParameterPosition(paramPos) and DataFlowDispatch::parameterMatch(paramPos, argPos) and result = node.getArgumentAtPosition(argPos) ) diff --git a/shared/dataflow/codeql/dataflow/internal/FlowSummaryImpl.qll b/shared/dataflow/codeql/dataflow/internal/FlowSummaryImpl.qll index 3994abbf382d9..9451b0f88c402 100644 --- a/shared/dataflow/codeql/dataflow/internal/FlowSummaryImpl.qll +++ b/shared/dataflow/codeql/dataflow/internal/FlowSummaryImpl.qll @@ -8,35 +8,19 @@ private import DataFlowImpl /** * Provides language-specific parameters. */ -signature module InputStep1Sig { +signature module InputSig { /** - * A class of callables that are candidates for flow summary modeling. + * A base class of callables that are candidates for flow summary modeling. */ bindingset[this] - class SummarizedCallableBase; - - //{ - // bindingset[this] - // string toString(); - // /** Gets a call that targets this summarized callable. */ - // Lang::DataFlowCall getACall(); - // } - bindingset[c] - string summarizedCallableBaseToString(SummarizedCallableBase c); - - // /** - // * A class of callables that are candidates for neutral modeling. - // */ - // bindingset[this] // TODO: extends SummarizedCallableBase? - // class NeutralCallableBase { - // string toString(); - // /** Gets a call that targets this neutral callable. */ - // Lang::DataFlowCall getACall(); - // } + class SummarizedCallableBase { + bindingset[this] + string toString(); + } + /** Gets the parameter position representing a callback itself, if any. */ Lang::ArgumentPosition callbackSelfParameterPosition(); - // SummaryCall summaryDataFlowCall(SummaryNode receiver); /** Gets the type of content `c`. */ Lang::DataFlowType getContentType(Lang::ContentSet c); @@ -62,1737 +46,1689 @@ signature module InputStep1Sig { bindingset[t, rk] Lang::DataFlowType getCallbackReturnType(Lang::DataFlowType t, Lang::ReturnKind rk); - // Lang::DataFlowType getSyntheticGlobalType(SummaryComponent::SyntheticGlobal sg); - // /** - // * 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(SummarizedCallableBase c, string kind, string provenance); - // /** - // * Gets the summary component for specification component `c`, if any. - // * - // * This covers all the Ruby-specific components of a flow summary. - // */ - // bindingset[c] - // SummaryComponent interpretComponentSpecific(string c); - // /** Gets the textual representation of a summary component in the format used for MaD models. */ - // string getMadRepresentationSpecific(SummaryComponent sc); - /** Gets the textual representation of a parameter position in the format used for flow summaries. */ - string getParameterPosition(Lang::ParameterPosition pos); - - /** Gets the textual representation of an argument position in the format used for flow summaries. */ - string getArgumentPosition(Lang::ArgumentPosition pos); - /** Gets the return kind corresponding to specification `"ReturnValue"`. */ - Lang::ReturnKind getReturnValueKind(); - - /** Gets the argument position obtained by parsing `X` in `Parameter[X]`. */ - bindingset[s] - Lang::ArgumentPosition parseParamBody(string s); + Lang::ReturnKind getStandardReturnValueKind(); - /** Gets the parameter position obtained by parsing `X` in `Argument[X]`. */ - bindingset[s] - Lang::ParameterPosition parseArgBody(string s); -} + /** Gets the textual representation of parameter position `pos` used in MaD. */ + string encodeParameterPosition(Lang::ParameterPosition pos); -module MakeStep1 Input1> { - private import AccessPathSyntax as AccessPathSyntax - private import DataFlowLang - private import Input1 - private import codeql.dataflow.internal.DataFlowImplCommon::MakeImplCommon // as DataFlowImplCommon - private import codeql.util.Unit + /** Gets the textual representation of argument position `pos` used in MaD. */ + string encodeArgumentPosition(Lang::ArgumentPosition pos); /** - * Provides language-specific parameters. + * Gets the textual representation of content `c` used in MaD. + * + * `arg` will be printed in square brackets (`[]`) after the result, unless + * `arg` is the empty string. */ - signature module InputStep2Sig { - // /** Gets the type of the parameter at the given position. */ - // DataFlowType getParameterType(Public::SummarizedCallable c, ParameterPosition pos); - // /** Gets the return type of kind `rk` for callable `c`. */ - // bindingset[c, rk] - // DataFlowType getReturnType(Public::SummarizedCallable c, ReturnKind rk); - /** - * Gets the summary component for specification component `c`, if any. - * - * This covers all the Ruby-specific components of a flow summary. - */ - bindingset[c] - default DataFlowLang::ContentSet interpretContentSummaryComponent( - AccessPathSyntax::AccessPathTokenBase c - ) { - none() - } - - bindingset[c] - default DataFlowLang::ArgumentPosition interpretParameterSummaryComponent( - AccessPathSyntax::AccessPathTokenBase c - ) { - none() - } + default string encodeContent(Lang::ContentSet c, string arg) { none() } - bindingset[c] - default DataFlowLang::ParameterPosition interpretArgumentSummaryComponent( - AccessPathSyntax::AccessPathTokenBase c - ) { - none() - } - - bindingset[c] - default DataFlowLang::ReturnKind interpretReturnSummaryComponent( - AccessPathSyntax::AccessPathTokenBase c - ) { - none() - } - - bindingset[c] - default DataFlowLang::ContentSet interpretWithoutContentSummaryComponent( - AccessPathSyntax::AccessPathTokenBase c - ) { - none() - } - - bindingset[c] - default DataFlowLang::ContentSet interpretWithContentSummaryComponent( - AccessPathSyntax::AccessPathTokenBase c - ) { - none() - } + /** + * 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() } - default string getContentMadRepresentation(ContentSet c) { none() } + /** + * 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() } - default string getParameterMadRepresentation(ArgumentPosition pos) { none() } + /** + * 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() } +} - default string getArgumentMadRepresentation(ParameterPosition pos) { none() } +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 - default string getReturnMadRepresentation(ReturnKind rk) { none() } + final private class SummarizedCallableBaseFinal = SummarizedCallableBase; - default string getWithoutContentMadRepresentation(ContentSet c) { none() } + /** Provides classes and predicates for defining flow summaries. */ + module Public { + private import Private - default string getWithContentMadRepresentation(ContentSet c) { none() } - } + /** + * 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 + ] + } - module MakeStep2 { - // private import InnerInput - final private class SummarizedCallableBaseFinal = SummarizedCallableBase; + /** + * 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; - /** Provides classes and predicates for defining flow summaries. */ - module Public { - private import Private + Provenance() { + exists(string origin | origin = getValidModelOrigin() | + this = origin + "-" + verification and + verification = ["manual", "generated"] + ) + or + this = verification and verification = "manual" + } /** - * Gets the valid model origin values. + * Holds if this is a valid generated provenance value. */ - private string getValidModelOrigin() { - result = - [ - "ai", // AI (machine learning) - "df", // Dataflow (model generator) - "tb", // Type based (model generator) - "hq", // Heuristic query - ] - } + predicate isGenerated() { verification = "generated" } /** - * 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. + * Holds if this is a valid manual provenance value. */ - class Provenance extends string { - private string verification; + predicate isManual() { verification = "manual" } + } - Provenance() { - exists(string origin | origin = getValidModelOrigin() | - this = origin + "-" + verification and - verification = ["manual", "generated"] - ) - or - this = verification and verification = "manual" - } + /** A callable with a flow summary. */ + abstract class SummarizedCallable extends SummarizedCallableBaseFinal { + bindingset[this] + SummarizedCallable() { any() } - /** - * Holds if this is a valid generated provenance value. - */ - predicate isGenerated() { verification = "generated" } + /** + * 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); - /** - * Holds if this is a valid manual provenance value. - */ - predicate isManual() { 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)) } - /** A callable with a flow summary. */ - abstract class SummarizedCallable extends SummarizedCallableBaseFinal { - bindingset[this] - SummarizedCallable() { any() } - - string toString() { result = summarizedCallableBaseToString(this) } - - /** - * 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); - - /** - * 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 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 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" } + /** + * 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() } - final private class NeutralCallableFinal = NeutralCallable; - /** - * A callable where there is no flow via the callable. + * Holds if there exists a manual summary that applies to this callable. */ - class NeutralSummaryCallable extends NeutralCallableFinal { - NeutralSummaryCallable() { this.getKind() = "summary" } + final predicate hasManualModel() { + exists(Provenance p | p.isManual() and this.hasProvenance(p)) } - // final private class NeutralCallableBaseFinal = NeutralCallableBase; /** - * A callable that has a neutral model. + * Holds if there exists a manual summary that applies to this callable. + * Always apply manual models if they exist. */ - abstract class NeutralCallable extends SummarizedCallableBaseFinal { - bindingset[this] - NeutralCallable() { exists(this) } - - string toString() { result = summarizedCallableBaseToString(this) } - - // private string kind; - // private Provenance provenance; - // NeutralCallable() { neutralElement(this, kind, provenance) } - /** - * Holds if the neutral is auto generated. - */ - final predicate hasGeneratedModel() { - any(Provenance p | this.hasProvenance(p)).isGenerated() - } + final predicate applyManualModel() { this.hasManualModel() } - /** - * Holds if there exists a manual neutral that applies to this callable. - */ - final predicate hasManualModel() { any(Provenance p | this.hasProvenance(p)).isManual() } + /** + * Holds if there exists a summary that applies to this callable + * that has provenance `provenance`. + */ + predicate hasProvenance(Provenance provenance) { provenance = "manual" } + } - /** - * Holds if the neutral has provenance `p`. - */ - abstract predicate hasProvenance(Provenance p); + final private class NeutralCallableFinal = NeutralCallable; - /** - * Gets the kind of the neutral. - */ - abstract string getKind(); - } + /** + * A callable where there is no flow via the callable. + */ + class NeutralSummaryCallable extends NeutralCallableFinal { + NeutralSummaryCallable() { this.getKind() = "summary" } } /** - * Provides predicates for compiling flow summaries down to atomic local steps, - * read steps, and store steps. + * A callable that has a neutral model. */ - module Private { - private import Public + abstract class NeutralCallable extends SummarizedCallableBaseFinal { + bindingset[this] + NeutralCallable() { exists(this) } /** - * A synthetic global. This represents some form of global state, which - * summaries can read and write individually. + * Holds if the neutral is auto generated. */ - abstract class SyntheticGlobal extends string { - bindingset[this] - SyntheticGlobal() { any() } + final predicate hasGeneratedModel() { + any(Provenance p | this.hasProvenance(p)).isGenerated() } - 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) - /** - * 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. + * Holds if there exists a manual neutral that applies to this callable. */ - class SummaryComponent instanceof TSummaryComponent { - /** Gets a textual representation of this component used for MaD models. */ - string getMadRepresentation() { - exists(ContentSet c | - this = TContentSummaryComponent(c) and - result = Input2::getContentMadRepresentation(c) - ) - or - exists(ArgumentPosition pos | this = TParameterSummaryComponent(pos) | - result = "Parameter[" + getArgumentPosition(pos) + "]" - or - result = Input2::getParameterMadRepresentation(pos) - ) - or - exists(ParameterPosition pos | - this = TArgumentSummaryComponent(pos) and - result = "Argument[" + getParameterPosition(pos) + "]" - or - result = Input2::getArgumentMadRepresentation(pos) - ) - or - exists(string synthetic | - this = TSyntheticGlobalSummaryComponent(synthetic) and - result = "SyntheticGlobal[" + synthetic + "]" - ) - or - exists(ReturnKind rk | this = TReturnSummaryComponent(rk) | - rk = getReturnValueKind() and result = "ReturnValue" - or - result = Input2::getReturnMadRepresentation(rk) - ) - or - exists(ContentSet c | - this = TWithoutContentSummaryComponent(c) and - result = Input2::getWithoutContentMadRepresentation(c) - ) - or - exists(ContentSet c | - this = TWithContentSummaryComponent(c) and - result = Input2::getWithContentMadRepresentation(c) - ) - } - - /** 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) } + final predicate hasManualModel() { any(Provenance p | this.hasProvenance(p)).isManual() } - /** Gets a summary component where data is not allowed to be stored in `c`. */ - SummaryComponent withoutContent(ContentSet c) { - result = TWithoutContentSummaryComponent(c) - } + /** + * Holds if the neutral has provenance `p`. + */ + abstract predicate hasProvenance(Provenance p); - /** Gets a summary component where data must be stored in `c`. */ - SummaryComponent withContent(ContentSet c) { result = TWithContentSummaryComponent(c) } + /** + * Gets the kind of the neutral. + */ + abstract string getKind(); + } + } - /** Gets a summary component for a parameter at position `pos`. */ - SummaryComponent parameter(ArgumentPosition pos) { - result = TParameterSummaryComponent(pos) - } + /** + * Provides predicates for compiling flow summaries down to atomic local steps, + * read steps, and store steps. + */ + module Private { + private import Public - /** Gets a summary component for an argument at position `pos`. */ - SummaryComponent argument(ParameterPosition pos) { result = TArgumentSummaryComponent(pos) } + /** + * 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 a summary component for a return of kind `rk`. */ - SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) } + 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) + + bindingset[name, arg] + private string encodeArg(string name, string arg) { + if arg = "" then result = name else result = name + "[" + arg + "]" + } - /** Gets a summary component for synthetic global `sg`. */ - SummaryComponent syntheticGlobal(SyntheticGlobal sg) { - result = TSyntheticGlobalSummaryComponent(sg) - } + /** + * 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 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) + ) } - private predicate summaryElement( - SummarizedCallable c, string input, string output, boolean preservesValue, string provenance - ) { - c.propagatesFlow(input, output, preservesValue) and - c.hasProvenance(provenance) - } + /** Gets a textual representation of this summary component. */ + string toString() { result = this.getMadRepresentation() } + } - /** Holds if `spec` is a relevant external specification. */ - private predicate relevantSpec(string spec) { - summaryElement(_, spec, _, _, _) or - summaryElement(_, _, spec, _, _) //or - // sourceElement(_, spec, _, _) or - // sinkElement(_, spec, _, _) - } + /** Provides predicates for constructing summary components. */ + module SummaryComponent { + /** Gets a summary component for content `c`. */ + SummaryComponent content(ContentSet c) { result = TContentSummaryComponent(c) } - import AccessPathSyntax::AccessPath + /** Gets a summary component where data is not allowed to be stored in `c`. */ + SummaryComponent withoutContent(ContentSet c) { result = TWithoutContentSummaryComponent(c) } - /** Holds if specification component `token` parses as parameter `pos`. */ - predicate parseParam(AccessPathToken token, ArgumentPosition pos) { - token.getName() = "Parameter" and - pos = parseParamBody(token.getAnArgument()) - } + /** Gets a summary component where data must be stored in `c`. */ + SummaryComponent withContent(ContentSet c) { result = TWithContentSummaryComponent(c) } - /** Holds if specification component `token` parses as argument `pos`. */ - predicate parseArg(AccessPathToken token, ParameterPosition pos) { - token.getName() = "Argument" and - pos = parseArgBody(token.getAnArgument()) - } + /** Gets a summary component for a parameter at position `pos`. */ + SummaryComponent parameter(ArgumentPosition pos) { result = TParameterSummaryComponent(pos) } - /** Holds if specification component `token` parses as synthetic global `sg`. */ - predicate parseSynthGlobal(AccessPathToken token, string sg) { - token.getName() = "SyntheticGlobal" and - sg = token.getAnArgument() - } + /** Gets a summary component for an argument at position `pos`. */ + SummaryComponent argument(ParameterPosition pos) { result = TArgumentSummaryComponent(pos) } - private class SyntheticGlobalFromAccessPath extends SyntheticGlobal { - SyntheticGlobalFromAccessPath() { parseSynthGlobal(_, this) } - } + /** Gets a summary component for a return of kind `rk`. */ + SummaryComponent return(ReturnKind rk) { result = TReturnSummaryComponent(rk) } - private TParameterSummaryComponent callbackSelfParam() { - result = TParameterSummaryComponent(callbackSelfParameterPosition()) + /** Gets a summary component for synthetic global `sg`. */ + SummaryComponent syntheticGlobal(SyntheticGlobal sg) { + result = TSyntheticGlobalSummaryComponent(sg) } + } - 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, _) - } + private predicate summaryElement( + SummarizedCallable c, string input, string output, boolean preservesValue, string provenance + ) { + c.propagatesFlow(input, output, preservesValue) and + c.hasProvenance(provenance) + } - /** - * 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, _) - } + private predicate summarySpec(string spec) { + summaryElement(_, spec, _, _, _) or + summaryElement(_, _, spec, _, _) + } - /** Gets the tail of this stack, if any. */ - SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) } + import AccessPathSyntax::AccessPath - /** Gets the length of this stack. */ - int length() { - this = TSingletonSummaryComponentStack(_) and result = 1 - or - result = 1 + this.tail().length() - } + /** Holds if specification component `token` parses as parameter `pos`. */ + predicate parseParam(AccessPathToken token, ArgumentPosition pos) { + token.getName() = "Parameter" and + token.getAnArgument() = encodeArgumentPosition(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) - } + /** Holds if specification component `token` parses as argument `pos`. */ + predicate parseArg(AccessPathToken token, ParameterPosition pos) { + token.getName() = "Argument" and + token.getAnArgument() = encodeParameterPosition(pos) + } - /** Holds if this stack contains summary component `c`. */ - predicate contains(SummaryComponent c) { c = this.drop(_).head() } + /** Holds if specification component `token` parses as synthetic global `sg`. */ + predicate parseSynthGlobal(AccessPathToken token, string sg) { + token.getName() = "SyntheticGlobal" and + sg = token.getAnArgument() + } - /** Gets the bottom element of this stack. */ - SummaryComponent bottom() { - this = TSingletonSummaryComponentStack(result) or result = this.tail().bottom() - } + private class SyntheticGlobalFromAccessPath extends SyntheticGlobal { + SyntheticGlobalFromAccessPath() { parseSynthGlobal(_, this) } + } - /** 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() - ) - } + private TParameterSummaryComponent callbackSelfParam() { + result = TParameterSummaryComponent(callbackSelfParameterPosition()) + } - /** Gets a textual representation of this stack. */ - string toString() { result = this.getMadRepresentation() } + 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, _) } - /** 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) - } + /** + * 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 a singleton stack for an argument at position `pos`. */ - SummaryComponentStack argument(ParameterPosition pos) { - result = singleton(SummaryComponent::argument(pos)) - } + /** Gets the tail of this stack, if any. */ + SummaryComponentStack tail() { this = TConsSummaryComponentStack(_, result) } - /** Gets a singleton stack representing a return of kind `rk`. */ - SummaryComponentStack return(ReturnKind rk) { - result = singleton(SummaryComponent::return(rk)) - } + /** Gets the length of this stack. */ + int length() { + this = TSingletonSummaryComponentStack(_) and result = 1 + or + result = 1 + this.tail().length() } - /** - * 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 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) } - /** A callable with a flow summary. */ - abstract class SummarizedCallableImpl extends SummarizedCallableBaseFinal { - bindingset[this] - SummarizedCallableImpl() { any() } - - string toString() { result = summarizedCallableBaseToString(this) } + /** Holds if this stack contains summary component `c`. */ + predicate contains(SummaryComponent c) { c = this.drop(_).head() } - /** - * 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 there exists a summary that applies to this callable - * that has provenance `provenance`. - */ - abstract predicate hasProvenance(Provenance provenance); + /** Gets the bottom element of this stack. */ + SummaryComponent bottom() { + this = TSingletonSummaryComponentStack(result) or result = this.tail().bottom() } - 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 - 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) - ) + /** 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 - // 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) + exists(SummaryComponent c | + this = TSingletonSummaryComponentStack(c) and + result = c.getMadRepresentation() ) } + /** 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) + } + /** - * 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. + * Gets the stack obtained by pushing `head` onto `tail`. * - * 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. + * Make sure to override `RequiredSummaryComponentStack::required()` in order + * to ensure that the constructed stack exists. */ - 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(_) - ) + SummaryComponentStack push(SummaryComponent head, SummaryComponentStack tail) { + result = TConsSummaryComponentStack(head, tail) } - 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 singleton stack for an argument at position `pos`. */ + SummaryComponentStack argument(ParameterPosition pos) { + result = singleton(SummaryComponent::argument(pos)) } - private predicate isCallbackParameter(SummaryComponentStack s) { - s.head() = TParameterSummaryComponent(_) and exists(s.tail()) + /** Gets a singleton stack representing a return of kind `rk`. */ + SummaryComponentStack return(ReturnKind rk) { + result = singleton(SummaryComponent::return(rk)) } + } - private predicate isContentOfArgument(SummaryComponentStack s, ParameterPosition pos) { - s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail(), pos) - or - s = 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 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) - } - - 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) } + /** A callable with a flow summary. */ + abstract class SummarizedCallableImpl extends SummarizedCallableBaseFinal { + bindingset[this] + SummarizedCallableImpl() { any() } /** - * A state used to break up (complex) flow summaries into atomic flow steps. - * For a flow summary + * Holds if data may flow from `input` to `output` through this callable. * - * ```ql - * propagatesFlow( - * SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue - * ) - * ``` + * `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(_)`. * - * the following states are used: + * Output stacks ending with `SummaryComponent::return(_)` can be preceded by zero + * or more `SummaryComponent::content(_)` components. * - * - `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. + * 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. */ - 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) - } + pragma[nomagic] + abstract predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ); - /** 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) - } + /** + * Holds if there exists a summary that applies to this callable + * that has provenance `provenance`. + */ + abstract predicate hasProvenance(Provenance provenance); + } - /** 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 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 + 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) + ) + } + + /** + * 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(_) + ) + } + + 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) + 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) + } + + 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) } + + /** + * 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 newtype TSummaryNode = - TSummaryInternalNode(SummarizedCallable c, SummaryNodeState state) { - summaryNodeRange(c, state) - } or - TSummaryParameterNode(SummarizedCallable c, ParameterPosition pos) { - summaryParameterNodeRange(c, pos) - } + /** 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) + } - abstract class SummaryNode extends TSummaryNode { - abstract string toString(); + /** 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 + ) + } + } - abstract SummarizedCallable getSummarizedCallable(); + private newtype TSummaryNode = + TSummaryInternalNode(SummarizedCallable c, SummaryNodeState state) { + summaryNodeRange(c, state) + } or + TSummaryParameterNode(SummarizedCallable c, ParameterPosition pos) { + summaryParameterNodeRange(c, pos) } - private class SummaryInternalNode extends SummaryNode, TSummaryInternalNode { - private SummarizedCallable c; - private SummaryNodeState state; + abstract class SummaryNode extends TSummaryNode { + abstract string toString(); - SummaryInternalNode() { this = TSummaryInternalNode(c, state) } + abstract SummarizedCallable getSummarizedCallable(); + } - override string toString() { result = "[summary] " + state + " in " + c } + private class SummaryInternalNode extends SummaryNode, TSummaryInternalNode { + private SummarizedCallable c; + private SummaryNodeState state; - override SummarizedCallable getSummarizedCallable() { result = c } - } + SummaryInternalNode() { this = TSummaryInternalNode(c, state) } - private class SummaryParamNode extends SummaryNode, TSummaryParameterNode { - private SummarizedCallable c; - private ParameterPosition pos; + override string toString() { result = "[summary] " + state + " in " + c } - SummaryParamNode() { this = TSummaryParameterNode(c, pos) } + override SummarizedCallable getSummarizedCallable() { result = c } + } - override string toString() { result = "[summary param] " + pos + " in " + c } + private class SummaryParamNode extends SummaryNode, TSummaryParameterNode { + private SummarizedCallable c; + private ParameterPosition pos; - override SummarizedCallable getSummarizedCallable() { result = c } - } + 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, _) + 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) + ) + ) + } + 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, _) + or + callbackInput(c, _, receiver, _) + } + + /** 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 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) + ) + } + + /** 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) + ) + } + + /** 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 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)) + ) + } + + signature module StepsInputSig { + DataFlowType getSyntheticGlobalType(SyntheticGlobal sg); + + DataFlowCall getACall(SummarizedCallable sc); + } + + /** Provides a compilation of flow summaries to atomic data-flow steps. */ + module Steps { /** - * 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. + * Gets the type of synthesized summary node `n`. + * + * The type is computed based on the language-specific predicates + * `getContentType()`, `getReturnType()`, `getCallbackParameterType()`, and + * `getCallbackReturnType()`. */ - private predicate parameterReadState( - SummarizedCallable c, SummaryNodeState state, ParameterPosition pos - ) { - state.isInputState(c, SummaryComponentStack::argument(pos)) + 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) + ) + ) + ) } /** - * Holds if a synthesized summary node is needed for the state `state` in summarized - * callable `c`. + * Holds if there is a local step from `pred` to `succ`, which is synthesized + * from a flow summary. */ - private predicate summaryNodeRange(SummarizedCallable c, SummaryNodeState state) { - state.isInputState(c, _) and - not parameterReadState(c, state, _) + 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 - state.isOutputState(c, _) + 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 + ) } - 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 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() ) } - 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 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 a write targets `post`, which is a post-update node for a - * parameter at position `pos` in `c`. + * Holds if there is a jump step from `pred` to `succ`, which is synthesized + * from a flow summary. */ - private predicate isParameterPostUpdate( - SummaryNode post, SummarizedCallable c, ParameterPosition pos - ) { - post = summaryNodeOutputState(c, SummaryComponentStack::argument(pos)) + predicate summaryJumpStep(SummaryNode pred, SummaryNode succ) { + exists(SummaryComponentStack s | + s = SummaryComponentStack::singleton(SummaryComponent::syntheticGlobal(_)) and + pred = summaryNodeOutputState(_, s) and + succ = summaryNodeInputState(_, s) + ) } - /** 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)) + /** + * 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) + ) } - 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 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) + ) } - private predicate callbackInput( - SummarizedCallable c, SummaryComponentStack s, SummaryNode receiver, ArgumentPosition pos + pragma[noinline] + private predicate viableParam( + DataFlowCall call, SummarizedCallable sc, ParameterPosition ppos, SummaryParamNode p ) { - any(SummaryNodeState state).isOutputState(c, s) and - s.head() = TParameterSummaryComponent(pos) and - receiver = summaryNodeInputState(c, s.tail()) + p = TSummaryParameterNode(sc, ppos) and + call = StepsInput::getACall(sc) } - /** Holds if a call targeting `receiver` should be synthesized inside `c`. */ - predicate summaryCallbackRange(SummarizedCallable c, SummaryNode receiver) { - callbackOutput(c, _, receiver, _) + 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 - callbackInput(c, _, receiver, _) + 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 summary node `p` is a parameter with position `pos`. */ - predicate summaryParameterNode(SummaryNode p, ParameterPosition pos) { - p = TSummaryParameterNode(_, pos) + /** + * 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 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) + 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 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) + 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) ) } - /** 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) + /** + * 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)) ) - or - exists(SummarizedCallable callable, SummaryComponentStack s | - callbackInput(callable, s, _, _) and - pre = summaryNodeOutputState(callable, s) and - post = summaryNodeInputState(callable, s) + } + + /** + * 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) ) } - /** 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 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, _) ) } /** - * 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. + * 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 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)) + 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) ) } + } - signature module InputStep3Sig { - DataFlowType getSyntheticGlobalType(SyntheticGlobal sg); + /** + * 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, "") + | + result = SummaryComponent::content(c) + ) + or + 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(getStandardReturnValueKind()) + or + exists(ReturnKind rk | + exists(string name | name = encodeReturn(rk, token.getAnArgument(name))) or + token = encodeReturn(rk, "") + | + result = SummaryComponent::return(rk) + ) + or + exists(string sg | + parseSynthGlobal(token, sg) and result = SummaryComponent::syntheticGlobal(sg) + ) + or + 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) + ) + } - DataFlowCall getACall(SummarizedCallable sc); + /** + * Holds if `spec` specifies summary component stack `stack`. + */ + predicate interpretSpec(AccessPath spec, SummaryComponentStack stack) { + interpretSpec(spec, spec.getNumToken(), stack) } - module MakeStep3 { - /** - * 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 = Input3::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 = Input3::getSyntheticGlobalType(sg) - ) - ) - ) - } + /** 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) + ) + } - /** 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 - ) { - p = TSummaryParameterNode(sc, ppos) and - call = Input3::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) - ) - } - - /** - * 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)) - ) - } + /** 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)) + } - 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) - ) - } - - /** - * 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)) - ) - } - - /** - * 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) - ) - } - - /** - * 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, _) - ) - } - - /** - * 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 class MkStack extends RequiredSummaryComponentStack { + override predicate required(SummaryComponent head, SummaryComponentStack tail) { + interpretSpec(_, _, head, tail) } } - /** - * Provides a means of translating externally (e.g., MaD) defined flow - * summaries into a `SummarizedCallable`s. - */ - module External { - private SummaryComponent interpretComponent(AccessPathToken token) { - result = SummaryComponent::content(Input2::interpretContentSummaryComponent(token)) - or - exists(ParameterPosition pos | - parseArg(token, pos) and - result = SummaryComponent::argument(pos) - ) - or - result = SummaryComponent::argument(Input2::interpretArgumentSummaryComponent(token)) - or - exists(ArgumentPosition pos | - parseParam(token, pos) and result = SummaryComponent::parameter(pos) - ) - or - result = SummaryComponent::parameter(Input2::interpretParameterSummaryComponent(token)) - or - token = "ReturnValue" and result = SummaryComponent::return(getReturnValueKind()) - or - result = SummaryComponent::return(Input2::interpretReturnSummaryComponent(token)) - or - exists(string sg | - parseSynthGlobal(token, sg) and result = SummaryComponent::syntheticGlobal(sg) - ) - or - result = - SummaryComponent::withoutContent(Input2::interpretWithoutContentSummaryComponent(token)) - or - result = - SummaryComponent::withContent(Input2::interpretWithContentSummaryComponent(token)) - } + private class SummarizedCallableExternal extends SummarizedCallableImpl instanceof SummarizedCallable + { + SummarizedCallableExternal() { summaryElement(this, _, _, _, _) } - /** - * Holds if `spec` specifies summary component stack `stack`. - */ - predicate interpretSpec(AccessPath spec, SummaryComponentStack stack) { - interpretSpec(spec, spec.getNumToken(), stack) + 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() } - /** 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) + 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) } - /** 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 + override predicate propagatesFlow( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue ) { - interpretSpec(spec, n - 1, tail) and - head = interpretComponent(spec.getToken(n - 1)) + exists(AccessPath inSpec, AccessPath outSpec | + this.relevantSummaryElement(inSpec, outSpec, preservesValue) and + interpretSpec(inSpec, input) and + interpretSpec(outSpec, output) + ) } - private class MkStack extends RequiredSummaryComponentStack { - override predicate required(SummaryComponent head, SummaryComponentStack tail) { - interpretSpec(_, _, head, tail) - } + override predicate hasProvenance(Provenance provenance) { + summaryElement(this, _, _, _, provenance) } + } - 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) - ) - } + /** Holds if component `c` of specification `spec` cannot be parsed. */ + predicate invalidSpecComponent(AccessPath spec, string c) { + c = spec.getToken(_) and + not exists(interpretComponent(c)) + } - override predicate hasProvenance(Provenance provenance) { - summaryElement(this, _, _, _, provenance) - } - } + /** Holds if `provenance` is not a valid provenance value. */ + bindingset[provenance] + predicate invalidProvenance(string provenance) { not provenance instanceof 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 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 + AccessPathSyntax::parseInt(part.getArgumentList()) < 0 + } + + signature module SourceSinkInterpretationInputSig { + class Element { + string toString(); } - /** Holds if `provenance` is not a valid provenance value. */ - bindingset[provenance] - predicate invalidProvenance(string provenance) { not provenance instanceof Provenance } + /** + * 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); /** - * Holds if token `part` of specification `spec` has an invalid index. - * E.g., `Argument[-1]`. + * Holds if an external sink specification exists for `n` with input specification + * `input`, kind `kind` and provenance `provenance`. */ - predicate invalidIndexComponent(AccessPath spec, AccessPathToken part) { - part = spec.getToken(_) and - part.getName() = ["Parameter", "Argument"] and - AccessPathSyntax::parseInt(part.getArgumentList()) < 0 - } + predicate sinkElement(Element n, string input, string kind, string provenance); - signature module SinkInterpretationInputSig { - class Element { - string toString(); - } + class SourceOrSinkElement extends Element; - /** - * 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); + /** 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 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); + /** Gets the data-flow node that this node corresponds to, if any. */ + DataFlowLang::Node asNode(); - class SourceOrSinkElement extends Element; + /** Gets the call that this node corresponds to, if any. */ + DataFlowLang::DataFlowCall asCall(); - /** 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(); + /** Gets the callable that this node corresponds to, if any. */ + DataFlowLang::DataFlowCallable asCallable(); - /** Gets the data-flow node that this node corresponds to, if any. */ - DataFlowLang::Node asNode(); + /** Gets the target of this call, if any. */ + Element getCallTarget(); + } - /** Gets the call that this node corresponds to, if any. */ - DataFlowLang::DataFlowCall asCall(); + /** Provides additional sink specification logic. */ + predicate interpretOutput(string c, InterpretNode mid, InterpretNode node); - /** Gets the callable that this node corresponds to, if any. */ - DataFlowLang::DataFlowCallable asCallable(); + /** Provides additional source specification logic. */ + predicate interpretInput(string c, InterpretNode mid, InterpretNode node); - /** Gets the target of this call, if any. */ - Element getCallTarget(); - } + /** Holds if output specification component `c` needs a reference. */ + predicate outputNeedsReference(string c); - /** Provides additional sink specification logic. */ - predicate interpretOutput(string c, InterpretNode mid, InterpretNode node); + /** Holds if input specification component `c` needs a reference. */ + predicate inputNeedsReference(string c); + } - /** Provides additional source specification logic. */ - predicate interpretInput(string c, InterpretNode mid, InterpretNode node); + /** + * 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, _, _) + } - /** Holds if output specification component `c` needs a reference. */ - predicate outputNeedsReference(string c); + private module AccessPath = AccessPathSyntax::AccessPath; - /** Holds if input specification component `c` needs a reference. */ - predicate inputNeedsReference(string c); + 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() + ) } - module SinkInterpretation { - private import SinkInterpretationInput + private predicate inputNeedsReferenceExt(SourceSinkAccessPathToken c) { + c.getName() = "Argument" or + inputNeedsReference(c) + } - private predicate outputNeedsReferenceExt(AccessPathToken c) { - c.getName() = ["Argument", "ReturnValue"] or - outputNeedsReference(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() + ) + } - private predicate sourceElementRef(InterpretNode ref, AccessPath 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(AccessPathToken c) { - c.getName() = "Argument" or - inputNeedsReference(c) - } - - private predicate sinkElementRef(InterpretNode ref, AccessPath 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( - 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 - SinkInterpretationInput::interpretOutput("", ref, node) - else node = ref + /** 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 + 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(InterpretNode mid, AccessPathToken c | - interpretOutput(output, n - 1, ref, mid) and - c = output.getToken(n - 1) + exists(ArgumentPosition apos, ParameterPosition ppos | + node.asNode().(ParamNode).isParameterOf(mid.asCallable(), ppos) and + parameterMatch(ppos, apos) | - 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 - SinkInterpretationInput::interpretOutput(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 - SinkInterpretationInput::interpretInput("", ref, node) - else node = ref + c = "Parameter" or parseParam(c, apos) ) or - exists(InterpretNode mid, AccessPathToken c | - interpretInput(input, n - 1, ref, mid) and - c = input.getToken(n - 1) + 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) | - 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 - SinkInterpretationInput::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, AccessPath output | - sourceElementRef(ref, output, kind) and - interpretOutput(output, output.getNumToken(), ref, node) + c = "Argument" or parseArg(c, ppos) ) - } - - /** - * 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) + 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) + ) } - } - /** 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(); - - /** 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() } + /** + * 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) + ) } - /** 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() } + /** + * 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) + ) } + } + } - /** 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" - } + /** 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(); - private string renderProvenance(SummarizedCallable c) { - if c.applyManualModel() then result = "manual" else c.hasProvenance(result) + /** Holds if flow is propagated between `input` and `output`. */ + predicate relevantSummary( + SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue + ) { + super.propagatesFlow(input, output, preservesValue) } - private string renderProvenanceNeutral(NeutralCallable c) { - if c.hasManualModel() then result = "manual" else c.hasProvenance(result) - } + string toString() { result = super.toString() } + } - /** - * 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 - ) - } + /** 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 a neutral model `csv` exists (semi-colon separated format). Used for testing purposes. - * The syntax is: "namespace;type;name;signature;kind;provenance"", + * Gets the kind of the neutral. */ - query predicate neutral(string csv) { - exists(RelevantNeutralCallable c | - csv = - c.getCallableCsv() // Callable information - + c.getKind() + ";" // kind - + renderProvenanceNeutral(c) // provenance - ) - } + 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) } /** - * 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`. + * 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. */ - module RenderSummarizedCallable { - private module PrivateSteps = Private::MakeStep3::Steps; - - /** A summarized callable to include in the graph. */ - abstract class RelevantSummarizedCallable instanceof SummarizedCallable { - string toString() { result = super.toString() } - } + 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 + ) + } - 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, _) - ) - } + /** + * 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 class NodeOrCall extends TNodeOrCall { - SummaryNode asNode() { this = MkNode(result) } + /** + * 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; - SummaryNode asCallReceiver() { this = MkCall(result) } + /** A summarized callable to include in the graph. */ + abstract class RelevantSummarizedCallable instanceof SummarizedCallable { + string toString() { result = super.toString() } + } - string toString() { - result = this.asNode().toString() + private newtype TNodeOrCall = + MkNode(SummaryNode n) { + exists(RelevantSummarizedCallable c | + n = TSummaryInternalNode(c, _) 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 - } + n = TSummaryParameterNode(c, _) + ) + } or + MkCall(SummaryNode receiver) { + receiver.getSummarizedCallable() instanceof RelevantSummarizedCallable and + ( + callbackInput(_, _, receiver, _) or + callbackOutput(_, _, receiver, _) + ) } - query predicate nodes(NodeOrCall n, string key, string val) { - key = "semmle.label" and val = n.toString() - } + private class NodeOrCall extends TNodeOrCall { + SummaryNode asNode() { this = MkNode(result) } - 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" - ) + SummaryNode asCallReceiver() { this = MkCall(result) } + + string toString() { + result = this.asNode().toString() or - 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 + ")" - ) + 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 + exists(ContentSet c | + PrivateSteps::summaryReadStep(a.asNode(), c, b.asNode()) and + value = "read (" + c + ")" or - summaryPostUpdateNode(b.asNode(), a.asNode()) and - value = "post-update" + PrivateSteps::summaryStoreStep(a.asNode(), c, b.asNode()) and + value = "store (" + c + ")" or - b.asCallReceiver() = a.asNode() and - value = "receiver" + PrivateSteps::summaryClearsContent(a.asNode(), c) and + b = a and + value = "clear (" + c + ")" or - exists(ArgumentPosition pos | - summaryArgumentNode(b.asCallReceiver(), a.asNode(), pos) and - value = "argument (" + pos + ")" - ) - } + PrivateSteps::summaryExpectsContent(a.asNode(), c) and + b = a and + value = "expect (" + c + ")" + ) + or + summaryPostUpdateNode(b.asNode(), a.asNode()) and + value = "post-update" + or + 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, " / ") } } }